summaryrefslogtreecommitdiff
path: root/program/include
diff options
context:
space:
mode:
Diffstat (limited to 'program/include')
-rw-r--r--program/include/bc.php (renamed from program/include/rcube_bc.inc)39
-rw-r--r--program/include/clisetup.php68
-rw-r--r--program/include/html.php848
-rw-r--r--program/include/iniset.php61
-rw-r--r--program/include/rcmail.php100
-rw-r--r--program/include/rcmail_html_page.php (renamed from program/include/rcube_result_set.php)57
-rw-r--r--program/include/rcmail_output.php120
-rw-r--r--program/include/rcmail_output_html.php (renamed from program/include/rcube_output_html.php)139
-rw-r--r--program/include/rcmail_output_json.php (renamed from program/include/rcube_output_json.php)9
-rw-r--r--program/include/rcmail_string_replacer.php54
-rw-r--r--program/include/rcube.php1251
-rw-r--r--program/include/rcube_addressbook.php532
-rw-r--r--program/include/rcube_base_replacer.php107
-rw-r--r--program/include/rcube_browser.php72
-rw-r--r--program/include/rcube_cache.php559
-rw-r--r--program/include/rcube_charset.php763
-rw-r--r--program/include/rcube_config.php418
-rw-r--r--program/include/rcube_contacts.php1001
-rw-r--r--program/include/rcube_content_filter.php57
-rw-r--r--program/include/rcube_db.php1010
-rw-r--r--program/include/rcube_db_mssql.php157
-rw-r--r--program/include/rcube_db_mysql.php156
-rw-r--r--program/include/rcube_db_pgsql.php137
-rw-r--r--program/include/rcube_db_sqlite.php180
-rw-r--r--program/include/rcube_db_sqlsrv.php158
-rw-r--r--program/include/rcube_image.php251
-rw-r--r--program/include/rcube_imap.php4167
-rw-r--r--program/include/rcube_imap_cache.php1160
-rw-r--r--program/include/rcube_imap_generic.php3701
-rw-r--r--program/include/rcube_ldap.php2323
-rw-r--r--program/include/rcube_message.php760
-rw-r--r--program/include/rcube_message_header.php287
-rw-r--r--program/include/rcube_message_part.php100
-rw-r--r--program/include/rcube_mime.php704
-rw-r--r--program/include/rcube_output.php283
-rw-r--r--program/include/rcube_plugin.php359
-rw-r--r--program/include/rcube_plugin_api.php494
-rw-r--r--program/include/rcube_result_index.php450
-rw-r--r--program/include/rcube_result_thread.php673
-rw-r--r--program/include/rcube_session.php649
-rw-r--r--program/include/rcube_shared.inc474
-rw-r--r--program/include/rcube_smtp.php469
-rw-r--r--program/include/rcube_spellchecker.php620
-rw-r--r--program/include/rcube_storage.php991
-rw-r--r--program/include/rcube_string_replacer.php194
-rw-r--r--program/include/rcube_user.php701
-rw-r--r--program/include/rcube_utils.php885
-rw-r--r--program/include/rcube_vcard.php825
48 files changed, 385 insertions, 29188 deletions
diff --git a/program/include/rcube_bc.inc b/program/include/bc.php
index 1894873e6..5047e0a84 100644
--- a/program/include/rcube_bc.inc
+++ b/program/include/bc.php
@@ -2,7 +2,7 @@
/*
+-----------------------------------------------------------------------+
- | program/include/main.inc |
+ | program/include/bc.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2012, The Roundcube Dev Team |
@@ -31,7 +31,8 @@ define('RCUBE_INPUT_GET', rcube_utils::INPUT_GET);
define('RCUBE_INPUT_POST', rcube_utils::INPUT_POST);
define('RCUBE_INPUT_GPC', rcube_utils::INPUT_GPC);
-define('JS_OBJECT_NAME', rcmail::JS_OBJECT_NAME);
+define('JS_OBJECT_NAME', rcmail_output::JS_OBJECT_NAME);
+define('RCMAIL_CHARSET', RCUBE_CHARSET);
function get_table_name($table)
{
@@ -83,19 +84,19 @@ function json_serialize($input)
return rcube_output::json_serialize($input);
}
-function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
+function rep_specialchars_output($str, $enctype='', $mode='', $newlines=true)
{
return rcube_utils::rep_specialchars_output($str, $enctype, $mode, $newlines);
}
-function Q($str, $mode='strict', $newlines=TRUE)
+function Q($str, $mode='strict', $newlines=true)
{
- return rcmail::Q($str, $mode, $newlines);
+ return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines);
}
function JQ($str)
{
- return rcmail::JQ($str);
+ return rcube_utils::rep_specialchars_output($str, 'js');
}
function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
@@ -125,7 +126,7 @@ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{
- return rcube_utils::get_edit_field($col, $value, $attrib, $type);
+ return rcube_output::get_edit_field($col, $value, $attrib, $type);
}
function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
@@ -338,9 +339,9 @@ function show_bytes($bytes)
return rcmail::get_instance()->show_bytes($bytes);
}
-function rc_wordwrap($string, $width=75, $break="\n", $cut=false)
+function rc_wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null)
{
- return rcube_mime::wordwrap($string, $width, $break, $cut);
+ return rcube_mime::wordwrap($string, $width, $break, $cut, $charset);
}
function rc_request_header($name)
@@ -382,3 +383,23 @@ function send_future_expire_header($offset = 2600000)
{
return rcmail::get_instance()->output->future_expire_header($offset);
}
+
+function get_opt($aliases = array())
+{
+ return rcube_utils::get_opt($aliases);
+}
+
+function prompt_silent($prompt = 'Password:')
+{
+ return rcube_utils::prompt_silent($prompt);
+}
+
+function get_boolean($str)
+{
+ return rcube_utils::get_boolean($str);
+}
+
+class rcube_html_page extends rcmail_html_page
+{
+
+}
diff --git a/program/include/clisetup.php b/program/include/clisetup.php
index a9af90a6f..07e318521 100644
--- a/program/include/clisetup.php
+++ b/program/include/clisetup.php
@@ -27,71 +27,3 @@ require_once INSTALL_PATH . 'program/include/iniset.php';
// Unset max. execution time limit, set to 120 seconds in iniset.php
@set_time_limit(0);
-
-/**
- * Parse commandline arguments into a hash array
- */
-function get_opt($aliases = array())
-{
- $args = array();
-
- for ($i=1; $i < count($_SERVER['argv']); $i++) {
- $arg = $_SERVER['argv'][$i];
- $value = true;
- $key = null;
-
- if ($arg[0] == '-') {
- $key = preg_replace('/^-+/', '', $arg);
- $sp = strpos($arg, '=');
- if ($sp > 0) {
- $key = substr($key, 0, $sp - 2);
- $value = substr($arg, $sp+1);
- }
- else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
- $value = $_SERVER['argv'][++$i];
- }
-
- $args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
- }
- else {
- $args[] = $arg;
- }
-
- if ($alias = $aliases[$key]) {
- $args[$alias] = $args[$key];
- }
- }
-
- return $args;
-}
-
-
-/**
- * from http://blogs.sitepoint.com/2009/05/01/interactive-cli-password-prompt-in-php/
- */
-function prompt_silent($prompt = "Password:")
-{
- if (preg_match('/^win/i', PHP_OS)) {
- $vbscript = sys_get_temp_dir() . 'prompt_password.vbs';
- file_put_contents($vbscript, 'wscript.echo(InputBox("' . addslashes($prompt) . '", "", "password here"))');
- $command = "cscript //nologo " . escapeshellarg($vbscript);
- $password = rtrim(shell_exec($command));
- unlink($vbscript);
- return $password;
- }
- else {
- $command = "/usr/bin/env bash -c 'echo OK'";
- if (rtrim(shell_exec($command)) !== 'OK') {
- echo $prompt;
- $pass = trim(fgets(STDIN));
- echo chr(8)."\r" . $prompt . str_repeat("*", strlen($pass))."\n";
- return $pass;
- }
- $command = "/usr/bin/env bash -c 'read -s -p \"" . addslashes($prompt) . "\" mypassword && echo \$mypassword'";
- $password = rtrim(shell_exec($command));
- echo "\n";
- return $password;
- }
-}
-
-?>
diff --git a/program/include/html.php b/program/include/html.php
deleted file mode 100644
index 880873ddc..000000000
--- a/program/include/html.php
+++ /dev/null
@@ -1,848 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/html.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-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: |
- | Helper class to create valid XHTML code |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Class for HTML code creation
- *
- * @package HTML
- */
-class html
-{
- protected $tagname;
- protected $attrib = array();
- protected $allowed = array();
- protected $content;
-
- public static $doctype = 'xhtml';
- public static $lc_tags = true;
- public static $common_attrib = array('id','class','style','title','align');
- public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script');
-
- /**
- * 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_attrib List with allowed attributes, omit to allow all
- * @return string The XHTML tag
- */
- public static function tag($tagname, $attrib = array(), $content = null, $allowed_attrib = 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_attrib) . '>' . $content . $suffix;
- }
- else {
- return '<' . $tagname . self::attrib_string($attrib, $allowed_attrib) . '>' . $suffix;
- }
- }
-
- /**
- *
- */
- 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')));
- }
-
- /**
- * 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')));
- }
-
- /**
- * 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')));
- }
-
- /**
- * 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
- *
- * @return string HTML code
- * @see html::tag()
- */
- public static function br()
- {
- return self::tag('br');
- }
-
- /**
- * Create string with attributes
- *
- * @param array $attrib Associative arry 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 not allowed attributes
- if ($key == 'nl' || ($allowed && !isset($allowed_f[$key])) || $value === null) {
- continue;
- }
-
- // skip empty eventhandlers
- if (preg_match('/^on[a-z]+/', $key) && !$value) {
- continue;
- }
-
- // attributes with no value
- if (in_array($key, array('checked', 'multiple', 'disabled', 'selected'))) {
- if ($value) {
- $attrib_arr[] = $key . '="' . $key . '"';
- }
- }
- 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 Input string
- * @return array Key-value pairs of parsed attributes
- */
- public static function parse_attrib_string($str)
- {
- $attrib = array();
- $regexp = '/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui';
-
- preg_match_all($regexp, stripslashes($str), $regs, PREG_SET_ORDER);
-
- // convert attributes to an associative array (name => value)
- if ($regs) {
- foreach ($regs as $attr) {
- $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
- }
- }
-
- return $attrib;
- }
-
- /**
- * Replacing specials characters in html attribute value
- *
- * @param string $str Input string
- *
- * @return string The quoted string
- */
- public static function quote($str)
- {
- return @htmlspecialchars($str, ENT_COMPAT, RCMAIL_CHARSET);
- }
-}
-
-
-/**
- * Class to create an HTML input field
- *
- * @package HTML
- */
-class html_inputfield extends html
-{
- protected $tagname = 'input';
- protected $type = 'text';
- protected $allowed = array(
- 'type','name','value','size','tabindex','autocapitalize',
- 'autocomplete','checked','onchange','onclick','disabled','readonly',
- 'spellcheck','results','maxlength','src','multiple','placeholder',
- );
-
- /**
- * 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 HTML
- */
-class html_passwordfield extends html_inputfield
-{
- protected $type = 'password';
-}
-
-/**
- * Class to create an hidden HTML input field
- *
- * @package HTML
- */
-
-class html_hiddenfield extends html
-{
- protected $tagname = 'input';
- protected $type = 'hidden';
- protected $fields_arr = array();
- protected $allowed = array('type','name','value','onchange','disabled','readonly');
-
- /**
- * 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_arr[] = $attrib;
- }
-
- /**
- * Create HTML code for the hidden fields
- *
- * @return string Final HTML code
- */
- public function show()
- {
- $out = '';
- foreach ($this->fields_arr as $attrib) {
- $out .= self::tag($this->tagname, array('type' => $this->type) + $attrib);
- }
- return $out;
- }
-}
-
-/**
- * Class to create HTML radio buttons
- *
- * @package HTML
- */
-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 HTML
- */
-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 HTML
- */
-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 HTML
- */
-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
- */
- public function add($names, $values = null)
- {
- if (is_array($names)) {
- foreach ($names as $i => $text) {
- $this->options[] = array('text' => $text, 'value' => $values[$i]);
- }
- }
- else {
- $this->options[] = array('text' => $names, 'value' => $values);
- }
- }
-
- /**
- * 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_content);
- }
-
- return parent::show();
- }
-}
-
-
-/**
- * Class to build an HTML table
- *
- * @package HTML
- */
-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);
- }
-
- /**
- * 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
- */
- 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;
- }
-
- $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('td', $col->attrib, $col->content);
- }
- $thead = self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib));
- }
-
- foreach ($this->rows as $r => $row) {
- $rowcontent = '';
- foreach ($row->cells as $c => $col) {
- $rowcontent .= self::tag('td', $col->attrib, $col->content);
- }
-
- if ($r < $this->rowindex || count($row->cells)) {
- $tbody .= self::tag('tr', $row->attrib, $rowcontent, parent::$common_attrib);
- }
- }
-
- if ($this->attrib['rowsonly']) {
- return $tbody;
- }
-
- // add <tbody>
- $this->content = $thead . self::tag('tbody', null, $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;
- }
-
-}
diff --git a/program/include/iniset.php b/program/include/iniset.php
index 82278c9f8..be71fc084 100644
--- a/program/include/iniset.php
+++ b/program/include/iniset.php
@@ -12,7 +12,7 @@
| See the README file for a full license statement. |
| |
| PURPOSE: |
- | Setup the application envoronment required to process |
+ | Setup the application environment required to process |
| any request. |
+-----------------------------------------------------------------------+
| Author: Till Klampaeckel <till@php.net> |
@@ -20,31 +20,25 @@
+-----------------------------------------------------------------------+
*/
+// application constants
+define('RCMAIL_VERSION', '0.9-git');
+define('RCMAIL_START', microtime(true));
+
$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,
'suhosin.session.encrypt' => 0,
'session.auto_start' => 0,
'file_uploads' => 1,
- 'magic_quotes_runtime' => 0,
- 'magic_quotes_sybase' => 0, // #1488506
);
foreach ($config as $optname => $optval) {
if ($optval != ini_get($optname) && @ini_set($optname, $optval) === false) {
die("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).\n"
- ."Read REQUIREMENTS section in INSTALL file or use Roundcube Installer!");
+ ."Check your PHP configuration (including php_admin_flag).");
}
}
-// application constants
-define('RCMAIL_VERSION', '0.9-git');
-define('RCMAIL_CHARSET', 'UTF-8');
-define('RCMAIL_START', microtime(true));
-
if (!defined('INSTALL_PATH')) {
define('INSTALL_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');
}
@@ -53,6 +47,14 @@ if (!defined('RCMAIL_CONFIG_DIR')) {
define('RCMAIL_CONFIG_DIR', INSTALL_PATH . 'config');
}
+if (!defined('RCUBE_LOCALIZATION_DIR')) {
+ define('RCUBE_LOCALIZATION_DIR', INSTALL_PATH . 'program/localization/');
+}
+
+define('RCUBE_INSTALL_PATH', INSTALL_PATH);
+define('RCUBE_CONFIG_DIR', RCMAIL_CONFIG_DIR.'/');
+
+
// RC include folders MUST be included FIRST to avoid other
// possible not compatible libraries (i.e PEAR) to be included
// instead the ones provided by RC
@@ -67,20 +69,29 @@ if (set_include_path($include_path) === false) {
// (does not work in safe mode)
@set_time_limit(120);
-// set internal encoding for mbstring extension
-if (extension_loaded('mbstring')) {
- mb_internal_encoding(RCMAIL_CHARSET);
- @mb_regex_encoding(RCMAIL_CHARSET);
-}
+// include Roundcube Framework
+require_once 'Roundcube/bootstrap.php';
-// include global functions
-require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
+// register autoloader for rcmail app classes
+spl_autoload_register('rcmail_autoload');
-// Register autoloader
-spl_autoload_register('rcube_autoload');
+// backward compatybility (to be removed)
+require_once INSTALL_PATH . 'program/include/bc.php';
-// set PEAR error handling (will also load the PEAR main class)
-PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error');
-// backward compatybility (to be removed)
-require_once INSTALL_PATH . 'program/include/rcube_bc.inc';
+/**
+ * PHP5 autoloader routine for dynamic class loading
+ */
+function rcmail_autoload($classname)
+{
+ if (strpos($classname, 'rcmail') === 0) {
+ $filepath = INSTALL_PATH . "program/include/$classname.php";
+ if (is_readable($filepath)) {
+ include_once $filepath;
+ return true;
+ }
+ }
+
+ return false;
+}
+
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index c2f76b388..8e01a2155 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -56,8 +56,6 @@ class rcmail extends rcube
private $action_map = array();
- const JS_OBJECT_NAME = 'rcmail';
-
const ERROR_STORAGE = -2;
const ERROR_INVALID_REQUEST = 1;
const ERROR_INVALID_HOST = 2;
@@ -94,9 +92,6 @@ class rcmail extends rcube
// create user object
$this->set_user(new rcube_user($_SESSION['user_id']));
- // configure session (after user config merge!)
- $this->session_configure();
-
// set task and action properties
$this->set_task(rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC));
$this->action = asciiwords(rcube_utils::get_input_value('_action', rcube_utils::INPUT_GPC));
@@ -210,14 +205,22 @@ class rcmail extends rcube
}
}
+ // when user requested default writeable addressbook
+ // we need to check if default is writeable, if not we
+ // will return first writeable book (if any exist)
+ if ($contacts && $default && $contacts->readonly && $writeable) {
+ $contacts = null;
+ }
+
// Get first addressbook from the list if configured default doesn't exist
// This can happen when user deleted the addressbook (e.g. Kolab folder)
if (!$contacts && (!$id || $default)) {
- $source = reset($this->get_address_sources($writeable));
+ $source = reset($this->get_address_sources($writeable, !$default));
if (!empty($source)) {
$contacts = $this->get_address_book($source['id']);
- if ($contacts)
+ if ($contacts) {
$id = $source['id'];
+ }
}
}
@@ -229,16 +232,17 @@ class rcmail extends rcube
true, true);
}
+ // add to the 'books' array for shutdown function
+ $this->address_books[$id] = $contacts;
+
if ($writeable && $contacts->readonly) {
return null;
}
// set configured sort order
- if ($sort_col = $this->config->get('addressbook_sort_col'))
+ if ($sort_col = $this->config->get('addressbook_sort_col')) {
$contacts->set_sort_order($sort_col);
-
- // add to the 'books' array for shutdown function
- $this->address_books[$id] = $contacts;
+ }
return $contacts;
}
@@ -248,10 +252,11 @@ class rcmail extends rcube
* Return address books list
*
* @param boolean True if the address book needs to be writeable
+ * @param boolean True if the address book needs to be not hidden
*
* @return array Address books array
*/
- public function get_address_sources($writeable = false)
+ public function get_address_sources($writeable = false, $skip_hidden = false)
{
$abook_type = strtolower($this->config->get('address_book_type'));
$ldap_config = $this->config->get('ldap_public');
@@ -295,11 +300,17 @@ class rcmail extends rcube
foreach ($list as $idx => $item) {
// register source for shutdown function
- if (!is_object($this->address_books[$item['id']]))
+ if (!is_object($this->address_books[$item['id']])) {
$this->address_books[$item['id']] = $item;
+ }
// remove from list if not writeable as requested
- if ($writeable && $item['readonly'])
+ if ($writeable && $item['readonly']) {
unset($list[$idx]);
+ }
+ // remove from list if hidden as requested
+ else if ($skip_hidden && $item['hidden']) {
+ unset($list[$idx]);
+ }
}
return $list;
@@ -308,22 +319,21 @@ class rcmail extends rcube
/**
* Init output object for GUI and add common scripts.
- * This will instantiate a rcube_output_html object and set
+ * This will instantiate a rcmail_output_html object and set
* environment vars according to the current session and configuration
*
* @param boolean True if this request is loaded in a (i)frame
- * @return rcube_output_html Reference to HTML output object
+ * @return rcube_output Reference to HTML output object
*/
public function load_gui($framed = false)
{
// init output page
- if (!($this->output instanceof rcube_output_html))
- $this->output = new rcube_output_html($this->task, $framed);
+ if (!($this->output instanceof rcmail_output_html))
+ $this->output = new rcmail_output_html($this->task, $framed);
- // set keep-alive/check-recent interval
- if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
- $this->output->set_env('keep_alive', $keep_alive);
- }
+ // set refresh interval
+ $this->output->set_env('refresh_interval', $this->config->get('refresh_interval', 0));
+ $this->output->set_env('session_lifetime', $this->config->get('session_lifetime', 0) * 60);
if ($framed) {
$this->comm_path .= '&_framed=1';
@@ -333,10 +343,10 @@ class rcmail extends rcube
$this->output->set_env('task', $this->task);
$this->output->set_env('action', $this->action);
$this->output->set_env('comm_path', $this->comm_path);
- $this->output->set_charset(RCMAIL_CHARSET);
+ $this->output->set_charset(RCUBE_CHARSET);
// add some basic labels to client
- $this->output->add_label('loading', 'servererror', 'requesttimedout');
+ $this->output->add_label('loading', 'servererror', 'requesttimedout', 'refreshing');
return $this->output;
}
@@ -345,12 +355,12 @@ class rcmail extends rcube
/**
* Create an output object for JSON responses
*
- * @return rcube_output_json Reference to JSON output object
+ * @return rcube_output Reference to JSON output object
*/
public function json_init()
{
- if (!($this->output instanceof rcube_output_json))
- $this->output = new rcube_output_json($this->task);
+ if (!($this->output instanceof rcmail_output_json))
+ $this->output = new rcmail_output_json($this->task);
return $this->output;
}
@@ -522,7 +532,6 @@ class rcmail extends rcube
// Configure environment
$this->set_user($user);
$this->set_storage_prop();
- $this->session_configure();
// fix some old settings according to namespace prefix
$this->fix_namespace_settings($user);
@@ -542,9 +551,7 @@ class rcmail extends rcube
$_SESSION['login_time'] = time();
if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
- $_SESSION['timezone'] = floatval($_REQUEST['_timezone']);
- if (isset($_REQUEST['_dstactive']) && $_REQUEST['_dstactive'] != '_default_')
- $_SESSION['dst_active'] = intval($_REQUEST['_dstactive']);
+ $_SESSION['timezone'] = rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_GPC);
// force reloading complete list of subscribed mailboxes
$storage->clear_cache('mailboxes', true);
@@ -777,6 +784,7 @@ class rcmail extends rcube
}
}
+
/**
* Registers action aliases for current task
*
@@ -791,6 +799,7 @@ class rcmail extends rcube
}
}
+
/**
* Returns current action filename
*
@@ -805,6 +814,7 @@ class rcmail extends rcube
return strtr($this->action, '-', '_') . '.inc';
}
+
/**
* Fixes some user preferences according to namespace handling change.
* Old Roundcube versions were using folder names with removed namespace prefix.
@@ -1554,7 +1564,7 @@ class rcmail extends rcube
$html_name = $this->Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
$link_attrib = $folder['virtual'] ? array() : array(
'href' => $this->url(array('_mbox' => $folder['id'])),
- 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail::JS_OBJECT_NAME, $js_name),
+ 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail_output::JS_OBJECT_NAME, $js_name),
'rel' => $folder['id'],
'title' => $title,
);
@@ -1567,7 +1577,7 @@ class rcmail extends rcube
(!empty($folder['folders']) ? html::div(array(
'class' => ($is_collapsed ? 'collapsed' : 'expanded'),
'style' => "position:absolute",
- 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail::JS_OBJECT_NAME, $js_name)
+ 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail_output::JS_OBJECT_NAME, $js_name)
), '&nbsp;') : ''));
$jslist[$folder_id] = array(
@@ -2017,30 +2027,6 @@ class rcmail extends rcube
/**
- * Quote a given string.
- * Shortcut function for rcube_utils::rep_specialchars_output()
- *
- * @return string HTML-quoted string
- */
- public static function Q($str, $mode = 'strict', $newlines = true)
- {
- return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines);
- }
-
-
- /**
- * Quote a given string for javascript output.
- * Shortcut function for rcube_utils::rep_specialchars_output()
- *
- * @return string JS-quoted string
- */
- public static function JQ($str)
- {
- return rcube_utils::rep_specialchars_output($str, 'js');
- }
-
-
- /**
* Returns real size (calculated) of the message part
*
* @param rcube_message_part Message part
diff --git a/program/include/rcube_result_set.php b/program/include/rcmail_html_page.php
index 809d8743f..2624d590a 100644
--- a/program/include/rcube_result_set.php
+++ b/program/include/rcmail_html_page.php
@@ -2,17 +2,17 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_result_set.php |
+ | program/include/rcmail_html_page.php |
| |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2011, The Roundcube Dev Team |
+ | Copyright (C) 2006-2012, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
- | Class representing an address directory result set |
+ | Render a simple HTML page with the given contents |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
@@ -21,51 +21,16 @@
/**
- * Roundcube result set class.
- * Representing an address directory result set.
+ * Class to create HTML page output using a skin template
*
- * @package Addressbook
+ * @package Core
+ * @subpackage View
*/
-class rcube_result_set
+class rcmail_html_page extends rcmail_output_html
{
- var $count = 0;
- var $first = 0;
- var $current = 0;
- var $searchonly = false;
- var $records = array();
-
-
- 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++];
- }
-
- // alias for iterate()
- function next()
+ public function write($contents = '')
{
- return $this->iterate();
+ self::reset();
+ parent::write($contents);
}
-
- function seek($i)
- {
- $this->current = $i;
- }
-
-}
+} \ No newline at end of file
diff --git a/program/include/rcmail_output.php b/program/include/rcmail_output.php
new file mode 100644
index 000000000..36512ad48
--- /dev/null
+++ b/program/include/rcmail_output.php
@@ -0,0 +1,120 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcmail_output.php |
+ | |
+ | This file is part of the Roundcube PHP suite |
+ | Copyright (C) 2005-2012 The Roundcube Dev Team |
+ | |
+ | Licensed under the GNU General Public License version 3 or |
+ | any later version with exceptions for skins & plugins. |
+ | See the README file for a full license statement. |
+ | CONTENTS: |
+ | Abstract class for output generation |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ | Author: Aleksander Machniak <alec@alec.pl> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Class for output generation
+ *
+ * @package Core
+ * @subpackage View
+ */
+abstract class rcmail_output extends rcube_output
+{
+ const JS_OBJECT_NAME = 'rcmail';
+
+ public $type = 'html';
+ public $ajax_call = false;
+ public $framed = false;
+
+ protected $pagetitle = '';
+ protected $object_handlers = array();
+
+
+ /**
+ * Object constructor
+ */
+ public function __construct($task = null, $framed = false)
+ {
+ parent::__construct();
+ }
+
+
+ /**
+ * Setter for page title
+ *
+ * @param string $title Page title
+ */
+ public function set_pagetitle($title)
+ {
+ $this->pagetitle = $title;
+ }
+
+
+ /**
+ * Getter for the current skin path property
+ */
+ public function get_skin_path()
+ {
+ return $this->config->get('skin_path');
+ }
+
+
+ /**
+ * Delete all stored env variables and commands
+ */
+ public function reset()
+ {
+ parent::reset();
+
+ $this->object_handlers = array();
+ $this->pagetitle = '';
+ }
+
+
+ /**
+ * Call a client method
+ *
+ * @param string Method to call
+ * @param ... Additional arguments
+ */
+ abstract function command();
+
+
+ /**
+ * Add a localized label to the client environment
+ */
+ abstract function add_label();
+
+
+ /**
+ * Register a template object handler
+ *
+ * @param string Object name
+ * @param string Function name to call
+ * @return void
+ */
+ public function add_handler($obj, $func)
+ {
+ $this->object_handlers[$obj] = $func;
+ }
+
+
+ /**
+ * Register a list of template object handlers
+ *
+ * @param array Hash array with object=>handler pairs
+ * @return void
+ */
+ public function add_handlers($arr)
+ {
+ $this->object_handlers = array_merge($this->object_handlers, $arr);
+ }
+
+}
diff --git a/program/include/rcube_output_html.php b/program/include/rcmail_output_html.php
index d42171869..1290e173e 100644
--- a/program/include/rcube_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -2,7 +2,7 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcubeoutput_html.php |
+ | program/include/rcmail_output_html.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2006-2012, The Roundcube Dev Team |
@@ -23,9 +23,10 @@
/**
* Class to create HTML page output using a skin template
*
- * @package View
+ * @package Core
+ * @subpackage View
*/
-class rcube_output_html extends rcube_output
+class rcmail_output_html extends rcmail_output
{
public $type = 'html';
@@ -81,10 +82,10 @@ class rcube_output_html extends rcube_output
$this->set_env('extwin', 1);
// add common javascripts
- $this->add_script('var '.rcmail::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
+ $this->add_script('var '.self::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
// don't wait for page onload. Call init at the bottom of the page (delayed)
- $this->add_script(rcmail::JS_OBJECT_NAME.'.init();', 'docready');
+ $this->add_script(self::JS_OBJECT_NAME.'.init();', 'docready');
$this->scripts_path = 'program/js/';
$this->include_script('jquery.min.js');
@@ -206,6 +207,31 @@ class rcube_output_html extends rcube_output
/**
+ * Find the given file in the current skin path stack
+ *
+ * @param string File name/path to resolve (starting with /)
+ * @param string Reference to the base path of the matching skin
+ * @param string Additional path to search in
+ * @return mixed Relative path to the requested file or False if not found
+ */
+ public function get_skin_file($file, &$skin_path, $add_path = null)
+ {
+ $skin_paths = $this->skin_paths;
+ if ($add_path)
+ array_unshift($skin_paths, $add_path);
+
+ foreach ($skin_paths as $skin_path) {
+ $path = realpath($skin_path . $file);
+ if (is_file($path)) {
+ return $skin_path . $file;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
* Register a GUI object to the client script
*
* @param string Object name
@@ -214,7 +240,7 @@ class rcube_output_html extends rcube_output
*/
public function add_gui_object($obj, $id)
{
- $this->add_script(rcmail::JS_OBJECT_NAME.".gui_object('$obj', '$id');");
+ $this->add_script(self::JS_OBJECT_NAME.".gui_object('$obj', '$id');");
}
@@ -401,13 +427,13 @@ class rcube_output_html extends rcube_output
// apply skin search escalation list to plugin directory
$plugin_skin_paths = array();
foreach ($this->skin_paths as $skin_path) {
- $plugin_skin_paths[] = $this->app->plugins->dir . $plugin . '/' . $skin_path;
+ $plugin_skin_paths[] = $this->app->plugins->url . $plugin . '/' . $skin_path;
}
// add fallback to default skin
if (is_dir($this->app->plugins->dir . $plugin . '/skins/default')) {
$skin_dir = $plugin . '/skins/default';
- $plugin_skin_paths[] = $this->app->plugins->dir . $skin_dir;
+ $plugin_skin_paths[] = $this->app->plugins->url . $skin_dir;
}
// add plugin skin paths to search list
@@ -422,17 +448,20 @@ class rcube_output_html extends rcube_output
// fallback to deprecated template names
if (!is_readable($path) && $this->deprecated_templates[$realname]) {
$path = "$skin_path/templates/" . $this->deprecated_templates[$realname] . ".html";
- rcube::raise_error(array(
- 'code' => 502, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Using deprecated template '" . $this->deprecated_templates[$realname]
- . "' in $skin_path/templates. Please rename to '$realname'"),
- true, false);
+
+ if (is_readable($path)) {
+ rcube::raise_error(array(
+ 'code' => 502, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Using deprecated template '" . $this->deprecated_templates[$realname]
+ . "' in $skin_path/templates. Please rename to '$realname'"),
+ true, false);
+ }
}
if (is_readable($path)) {
$this->config->set('skin_path', $skin_path);
- $this->base_path = $skin_path;
+ $this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path); // set base_path to core skin directory (not plugin's skin)
break;
}
else {
@@ -469,8 +498,6 @@ class rcube_output_html extends rcube_output
$output = $hook['content'];
unset($hook['content']);
- $output = $this->parse_with_globals($this->fix_paths($output));
-
// make sure all <form> tags have a valid request token
$output = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $output);
$this->footer = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $this->footer);
@@ -509,7 +536,7 @@ class rcube_output_html extends rcube_output
{
$out = '';
if (!$this->framed && !empty($this->js_env)) {
- $out .= rcmail::JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
+ $out .= self::JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
}
if (!empty($this->js_labels)) {
$this->command('add_label', $this->js_labels);
@@ -522,7 +549,7 @@ class rcube_output_html extends rcube_output
$parent = $this->framed || preg_match('/^parent\./', $method);
$out .= sprintf(
"%s.%s(%s);\n",
- ($parent ? 'if(window.parent && parent.'.rcmail::JS_OBJECT_NAME.') parent.' : '') . rcmail::JS_OBJECT_NAME,
+ ($parent ? 'if(window.parent && parent.'.self::JS_OBJECT_NAME.') parent.' : '') . self::JS_OBJECT_NAME,
preg_replace('/^parent\./', '', $method),
implode(',', $args)
);
@@ -536,12 +563,17 @@ class rcube_output_html extends rcube_output
* Make URLs starting with a slash point to skin directory
*
* @param string Input string
+ * @param boolean True if URL should be resolved using the current skin path stack
* @return string
*/
- public function abs_url($str)
+ public function abs_url($str, $search_path = false)
{
- if ($str[0] == '/')
+ if ($str[0] == '/') {
+ if ($search_path && ($file_url = $this->get_skin_file($str, $skin_path)))
+ return $file_url;
+
return $this->base_path . $str;
+ }
else
return $str;
}
@@ -560,7 +592,7 @@ class rcube_output_html extends rcube_output
$ERROR_CODE = $code;
$ERROR_MESSAGE = $message;
- include INSTALL_PATH . 'program/steps/utils/error.inc';
+ include RCUBE_INSTALL_PATH . 'program/steps/utils/error.inc';
exit;
}
@@ -731,7 +763,7 @@ class rcube_output_html extends rcube_output
),
array(
"\$_SESSION['\\1']",
- "\$this->app->config->get('\\1',get_boolean('\\3'))",
+ "\$this->app->config->get('\\1',rcube_utils::get_boolean('\\3'))",
"\$this->env['\\1']",
"rcube_utils::get_input_value('\\1', rcube_utils::INPUT_GPC)",
"\$_COOKIE['\\1']",
@@ -803,7 +835,7 @@ class rcube_output_html extends rcube_output
unset($vars['name'], $vars['command']);
$label = $this->app->gettext($attrib + array('vars' => $vars));
- $quoting = !empty($attrib['quoting']) ? strtolower($attrib['quoting']) : (get_boolean((string)$attrib['html']) ? 'no' : '');
+ $quoting = !empty($attrib['quoting']) ? strtolower($attrib['quoting']) : (rcube_utils::get_boolean((string)$attrib['html']) ? 'no' : '');
switch ($quoting) {
case 'no':
@@ -811,7 +843,7 @@ class rcube_output_html extends rcube_output
break;
case 'javascript':
case 'js':
- $label = rcmail::JQ($label);
+ $label = rcube::JQ($label);
break;
default:
$label = html::quote($label);
@@ -825,15 +857,9 @@ class rcube_output_html extends rcube_output
// include a file
case 'include':
$old_base_path = $this->base_path;
- $skin_paths = $this->skin_paths;
- if ($attrib['skin_path'])
- array_unshift($skin_paths, $attrib['skin_path']);
- foreach ($skin_paths as $skin_path) {
- $path = realpath($skin_path . $attrib['file']);
- if (is_file($path)) {
- $this->base_path = $skin_path;
- break;
- }
+ if ($path = $this->get_skin_file($attrib['file'], $skin_path, $attrib['skinpath'])) {
+ $this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path); // set base_path to core skin directory (not plugin's skin)
+ $path = realpath($path);
}
if (is_readable($path)) {
@@ -895,11 +921,11 @@ class rcube_output_html extends rcube_output
}
else if ($object == 'version') {
$ver = (string)RCMAIL_VERSION;
- if (is_file(INSTALL_PATH . '.svn/entries')) {
+ if (is_file(RCUBE_INSTALL_PATH . '.svn/entries')) {
if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
$ver .= ' [SVN r'.$regs[1].']';
}
- else if (is_file(INSTALL_PATH . '.git/index')) {
+ else if (is_file(RCUBE_INSTALL_PATH . '.git/index')) {
if (preg_match('/Date:\s+([^\n]+)/', @shell_exec('git log -1'), $regs)) {
if ($date = date('Ymd.Hi', strtotime($regs[1]))) {
$ver .= ' [GIT '.$date.']';
@@ -1053,7 +1079,7 @@ class rcube_output_html extends rcube_output
if ($attrib['command']) {
$this->add_script(sprintf(
"%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
- rcmail::JS_OBJECT_NAME,
+ self::JS_OBJECT_NAME,
$command,
$attrib['id'],
$attrib['type'],
@@ -1065,7 +1091,7 @@ class rcube_output_html extends rcube_output
// make valid href to specific buttons
if (in_array($attrib['command'], rcmail::$main_tasks)) {
$attrib['href'] = $this->app->url(array('task' => $attrib['command']));
- $attrib['onclick'] = sprintf("%s.command('switch-task','%s',null,event); return false", rcmail::JS_OBJECT_NAME, $attrib['command']);
+ $attrib['onclick'] = sprintf("return %s.command('switch-task','%s',this,event)", self::JS_OBJECT_NAME, $attrib['command']);
}
else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
$attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
@@ -1089,7 +1115,7 @@ class rcube_output_html extends rcube_output
else if ($command && !$attrib['onclick']) {
$attrib['onclick'] = sprintf(
"return %s.command('%s','%s',this,event)",
- rcmail::JS_OBJECT_NAME,
+ self::JS_OBJECT_NAME,
$command,
$attrib['prop']
);
@@ -1333,11 +1359,13 @@ class rcube_output_html extends rcube_output
$output = substr_replace($output, $css, $pos, 0);
}
+ $output = $this->parse_with_globals($this->fix_paths($output));
+
// trigger hook with final HTML content to be sent
$hook = $this->app->plugins->exec_hook("send_page", array('content' => $output));
if (!$hook['abort']) {
- if ($this->charset != RCMAIL_CHARSET) {
- echo rcube_charset::convert($hook['content'], RCMAIL_CHARSET, $this->charset);
+ if ($this->charset != RCUBE_CHARSET) {
+ echo rcube_charset::convert($hook['content'], RCUBE_CHARSET, $this->charset);
}
else {
echo $hook['content'];
@@ -1350,21 +1378,25 @@ class rcube_output_html extends rcube_output
* Returns iframe object, registers some related env variables
*
* @param array $attrib HTML attributes
- *
+ * @param boolean $is_contentframe Register this iframe as the 'contentframe' gui object
* @return string IFRAME element
*/
- public function frame($attrib)
+ public function frame($attrib, $is_contentframe = false)
{
+ static $idcount = 0;
+
if (!$attrib['id']) {
- $attrib['id'] = 'rcmframe';
+ $attrib['id'] = 'rcmframe' . ++$idcount;
}
- if (!$attrib['name']) {
- $attrib['name'] = $attrib['id'];
- }
+ $attrib['name'] = $attrib['id'];
+ $attrib['src'] = $attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif';
- $this->set_env('contentframe', $attrib['id']);
- $this->set_env('blankpage', $attrib['src'] ? $this->abs_url($attrib['src']) : 'program/resources/blank.gif');
+ // register as 'contentframe' object
+ if ($is_contentframe || $attrib['contentframe']) {
+ $this->set_env('contentframe', $attrib['contentframe'] ? $attrib['contentframe'] : $attrib['name']);
+ $this->set_env('blankpage', $attrib['src']);
+ }
return html::iframe($attrib);
}
@@ -1493,7 +1525,6 @@ class rcube_output_html extends rcube_output
$input_task = new html_hiddenfield(array('name' => '_task', 'value' => 'login'));
$input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
$input_tzone = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
- $input_dst = new html_hiddenfield(array('name' => '_dstactive', 'id' => 'rcmlogindst', 'value' => '_default_'));
$input_url = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
$input_user = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser')
+ $attrib + $user_attrib);
@@ -1545,7 +1576,6 @@ class rcube_output_html extends rcube_output
$out = $input_task->show();
$out .= $input_action->show();
$out .= $input_tzone->show();
- $out .= $input_dst->show();
$out .= $input_url->show();
$out .= $table->show();
@@ -1558,6 +1588,9 @@ class rcube_output_html extends rcube_output
$out = $this->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
}
+ // include script for timezone detection
+ $this->include_script('jstz.min.js');
+
return $out;
}
@@ -1615,7 +1648,7 @@ class rcube_output_html extends rcube_output
if (empty($attrib['form'])) {
$out = $this->form_tag(array(
'name' => "rcmqsearchform",
- 'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search'); return false",
+ 'onsubmit' => self::JS_OBJECT_NAME . ".command('search'); return false",
'style' => "display:inline"),
$out);
}
@@ -1727,7 +1760,7 @@ class rcube_output_html extends rcube_output
'about.html',
);
foreach ($filenames as $file) {
- $fn = RCMAIL_CONFIG_DIR . '/' . $file;
+ $fn = RCUBE_CONFIG_DIR . $file;
if (is_readable($fn)) {
$content = file_get_contents($fn);
$content = $this->parse_conditions($content);
diff --git a/program/include/rcube_output_json.php b/program/include/rcmail_output_json.php
index eb1a9380d..def6ee42c 100644
--- a/program/include/rcube_output_json.php
+++ b/program/include/rcmail_output_json.php
@@ -2,7 +2,7 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_output_json.php |
+ | program/include/rcmail_output_json.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
@@ -23,9 +23,10 @@
/**
* View class to produce JSON responses
*
- * @package View
+ * @package Core
+ * @subpackage View
*/
-class rcube_output_json extends rcube_output
+class rcmail_output_json extends rcmail_output
{
protected $texts = array();
protected $commands = array();
@@ -157,7 +158,7 @@ class rcube_output_json extends rcube_output
{
$location = $this->app->url($p);
$this->remote_response(sprintf("window.setTimeout(function(){ %s.redirect('%s',true); }, %d);",
- rcmail::JS_OBJECT_NAME, $location, $delay));
+ self::JS_OBJECT_NAME, $location, $delay));
exit;
}
diff --git a/program/include/rcmail_string_replacer.php b/program/include/rcmail_string_replacer.php
new file mode 100644
index 000000000..4fbc611c9
--- /dev/null
+++ b/program/include/rcmail_string_replacer.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcmail_string_replacer.php |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 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: |
+ | Turn URLs and email addresses into clickable links |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+
+/**
+ * Helper class for turning URLs and email addresses in plaintext content
+ * into clickable links.
+ *
+ * @package Core
+ * @subpackage Utils
+ */
+class rcmail_string_replacer extends rcube_string_replacer
+{
+ /**
+ * Callback function used to build mailto: links around e-mail strings
+ *
+ * This also adds an onclick-handler to open the Rouncube compose message screen on such links
+ *
+ * @param array Matches result from preg_replace_callback
+ * @return int Index of saved string value
+ * @see rcube_string_replacer::mailto_callback()
+ */
+ public function mailto_callback($matches)
+ {
+ $href = $matches[1];
+ $suffix = $this->parse_url_brackets($href);
+
+ $i = $this->add(html::a(array(
+ 'href' => 'mailto:' . $href,
+ 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('compose','".rcube::JQ($href)."',this)",
+ ), rcube::Q($href)) . $suffix);
+
+ return $i >= 0 ? $this->get_replacement($i) : '';
+ }
+
+} \ No newline at end of file
diff --git a/program/include/rcube.php b/program/include/rcube.php
deleted file mode 100644
index 0e40b3c6b..000000000
--- a/program/include/rcube.php
+++ /dev/null
@@ -1,1251 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | |
- | 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: |
- | Framework base class providing core functions and holding |
- | instances of all 'global' objects like db- and storage-connections |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Base class of the Roundcube Framework
- * implemented as singleton
- *
- * @package Framework
- * @subpackage Core
- */
-class rcube
-{
- const INIT_WITH_DB = 1;
- const INIT_WITH_PLUGINS = 2;
-
- /**
- * Singleton instace of rcube
- *
- * @var rcmail
- */
- static protected $instance;
-
- /**
- * Stores instance of rcube_config.
- *
- * @var rcube_config
- */
- public $config;
-
- /**
- * Instace of database class.
- *
- * @var rcube_db
- */
- public $db;
-
- /**
- * Instace of Memcache class.
- *
- * @var Memcache
- */
- public $memcache;
-
- /**
- * Instace of rcube_session class.
- *
- * @var rcube_session
- */
- public $session;
-
- /**
- * Instance of rcube_smtp class.
- *
- * @var rcube_smtp
- */
- public $smtp;
-
- /**
- * Instance of rcube_storage class.
- *
- * @var rcube_storage
- */
- public $storage;
-
- /**
- * Instance of rcube_output class.
- *
- * @var rcube_output
- */
- public $output;
-
- /**
- * Instance of rcube_plugin_api.
- *
- * @var rcube_plugin_api
- */
- public $plugins;
-
-
- /* private/protected vars */
- protected $texts;
- protected $caches = array();
- protected $shutdown_functions = array();
- protected $expunge_cache = false;
-
-
- /**
- * This implements the 'singleton' design pattern
- *
- * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
- *
- * @return rcube The one and only instance
- */
- static function get_instance($mode = 0)
- {
- if (!self::$instance) {
- self::$instance = new rcube();
- self::$instance->init($mode);
- }
-
- return self::$instance;
- }
-
-
- /**
- * Private constructor
- */
- protected function __construct()
- {
- // load configuration
- $this->config = new rcube_config;
- $this->plugins = new rcube_dummy_plugin_api;
-
- register_shutdown_function(array($this, 'shutdown'));
- }
-
-
- /**
- * Initial startup function
- */
- protected function init($mode = 0)
- {
- // initialize syslog
- if ($this->config->get('log_driver') == 'syslog') {
- $syslog_id = $this->config->get('syslog_id', 'roundcube');
- $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
- openlog($syslog_id, LOG_ODELAY, $syslog_facility);
- }
-
- // connect to database
- if ($mode & self::INIT_WITH_DB) {
- $this->get_dbh();
- }
-
- // create plugin API and load plugins
- if ($mode & self::INIT_WITH_PLUGINS) {
- $this->plugins = rcube_plugin_api::get_instance();
- }
- }
-
-
- /**
- * Get the current database connection
- *
- * @return rcube_db Database object
- */
- public function get_dbh()
- {
- if (!$this->db) {
- $config_all = $this->config->all();
- $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
- $this->db->set_debug((bool)$config_all['sql_debug']);
- }
-
- return $this->db;
- }
-
-
- /**
- * Get global handle for memcache access
- *
- * @return object Memcache
- */
- public function get_memcache()
- {
- if (!isset($this->memcache)) {
- // no memcache support in PHP
- if (!class_exists('Memcache')) {
- $this->memcache = false;
- return false;
- }
-
- $this->memcache = new Memcache;
- $this->mc_available = 0;
-
- // add all configured hosts to pool
- $pconnect = $this->config->get('memcache_pconnect', true);
- foreach ($this->config->get('memcache_hosts', array()) as $host) {
- if (substr($host, 0, 7) != 'unix://') {
- list($host, $port) = explode(':', $host);
- if (!$port) $port = 11211;
- }
- else {
- $port = 0;
- }
-
- $this->mc_available += intval($this->memcache->addServer(
- $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
- }
-
- // test connection and failover (will result in $this->mc_available == 0 on complete failure)
- $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
-
- if (!$this->mc_available) {
- $this->memcache = false;
- }
- }
-
- return $this->memcache;
- }
-
-
- /**
- * Callback for memcache failure
- */
- public function memcache_failure($host, $port)
- {
- static $seen = array();
-
- // only report once
- if (!$seen["$host:$port"]++) {
- $this->mc_available--;
- self::raise_error(array(
- 'code' => 604, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => "Memcache failure on host $host:$port"),
- true, false);
- }
- }
-
-
- /**
- * Initialize and get cache object
- *
- * @param string $name Cache identifier
- * @param string $type Cache type ('db', 'apc' or 'memcache')
- * @param string $ttl Expiration time for cache items
- * @param bool $packed Enables/disables data serialization
- *
- * @return rcube_cache Cache object
- */
- public function get_cache($name, $type='db', $ttl=0, $packed=true)
- {
- if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
- $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
- }
-
- return $this->caches[$name];
- }
-
-
- /**
- * Create SMTP object and connect to server
- *
- * @param boolean True if connection should be established
- */
- public function smtp_init($connect = false)
- {
- $this->smtp = new rcube_smtp();
-
- if ($connect) {
- $this->smtp->connect();
- }
- }
-
-
- /**
- * Initialize and get storage object
- *
- * @return rcube_storage Storage object
- */
- public function get_storage()
- {
- // already initialized
- if (!is_object($this->storage)) {
- $this->storage_init();
- }
-
- return $this->storage;
- }
-
-
- /**
- * Initialize storage object
- */
- public function storage_init()
- {
- // already initialized
- if (is_object($this->storage)) {
- return;
- }
-
- $driver = $this->config->get('storage_driver', 'imap');
- $driver_class = "rcube_{$driver}";
-
- if (!class_exists($driver_class)) {
- self::raise_error(array(
- 'code' => 700, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Storage driver class ($driver) not found!"),
- true, true);
- }
-
- // Initialize storage object
- $this->storage = new $driver_class;
-
- // for backward compat. (deprecated, will be removed)
- $this->imap = $this->storage;
-
- // enable caching of mail data
- $storage_cache = $this->config->get("{$driver}_cache");
- $messages_cache = $this->config->get('messages_cache');
- // for backward compatybility
- if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
- $storage_cache = 'db';
- $messages_cache = true;
- }
-
- if ($storage_cache) {
- $this->storage->set_caching($storage_cache);
- }
- if ($messages_cache) {
- $this->storage->set_messages_caching(true);
- }
-
- // set pagesize from config
- $pagesize = $this->config->get('mail_pagesize');
- if (!$pagesize) {
- $pagesize = $this->config->get('pagesize', 50);
- }
- $this->storage->set_pagesize($pagesize);
-
- // set class options
- $options = array(
- 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'),
- 'auth_cid' => $this->config->get("{$driver}_auth_cid"),
- 'auth_pw' => $this->config->get("{$driver}_auth_pw"),
- 'debug' => (bool) $this->config->get("{$driver}_debug"),
- 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
- 'timeout' => (int) $this->config->get("{$driver}_timeout"),
- 'skip_deleted' => (bool) $this->config->get('skip_deleted'),
- 'driver' => $driver,
- );
-
- if (!empty($_SESSION['storage_host'])) {
- $options['host'] = $_SESSION['storage_host'];
- $options['user'] = $_SESSION['username'];
- $options['port'] = $_SESSION['storage_port'];
- $options['ssl'] = $_SESSION['storage_ssl'];
- $options['password'] = $this->decrypt($_SESSION['password']);
- $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
- }
-
- $options = $this->plugins->exec_hook("storage_init", $options);
-
- // for backward compat. (deprecated, to be removed)
- $options = $this->plugins->exec_hook("imap_init", $options);
-
- $this->storage->set_options($options);
- $this->set_storage_prop();
- }
-
-
- /**
- * Set storage parameters.
- * This must be done AFTER connecting to the server!
- */
- protected function set_storage_prop()
- {
- $storage = $this->get_storage();
-
- $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
-
- if ($default_folders = $this->config->get('default_folders')) {
- $storage->set_default_folders($default_folders);
- }
- if (isset($_SESSION['mbox'])) {
- $storage->set_folder($_SESSION['mbox']);
- }
- if (isset($_SESSION['page'])) {
- $storage->set_page($_SESSION['page']);
- }
- }
-
-
- /**
- * Create session object and start the session.
- */
- public function session_init()
- {
- // session started (Installer?)
- if (session_id()) {
- return;
- }
-
- $sess_name = $this->config->get('session_name');
- $sess_domain = $this->config->get('session_domain');
- $sess_path = $this->config->get('session_path');
- $lifetime = $this->config->get('session_lifetime', 0) * 60;
-
- // set session domain
- if ($sess_domain) {
- ini_set('session.cookie_domain', $sess_domain);
- }
- // set session path
- if ($sess_path) {
- ini_set('session.cookie_path', $sess_path);
- }
- // set session garbage collecting time according to session_lifetime
- if ($lifetime) {
- ini_set('session.gc_maxlifetime', $lifetime * 2);
- }
-
- ini_set('session.cookie_secure', rcube_utils::https_check());
- ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
- ini_set('session.use_cookies', 1);
- ini_set('session.use_only_cookies', 1);
- ini_set('session.serialize_handler', 'php');
- ini_set('session.cookie_httponly', 1);
-
- // use database for storing session data
- $this->session = new rcube_session($this->get_dbh(), $this->config);
-
- $this->session->register_gc_handler(array($this, 'temp_gc'));
- $this->session->register_gc_handler(array($this, 'cache_gc'));
-
- // start PHP session (if not in CLI mode)
- if ($_SERVER['REMOTE_ADDR']) {
- session_start();
- }
- }
-
-
- /**
- * Configure session object internals
- */
- public function session_configure()
- {
- if (!$this->session) {
- return;
- }
-
- $lifetime = $this->config->get('session_lifetime', 0) * 60;
- $keep_alive = $this->config->get('keep_alive');
-
- // set keep-alive/check-recent interval
- if ($keep_alive) {
- // be sure that it's less than session lifetime
- if ($lifetime) {
- $keep_alive = min($keep_alive, $lifetime - 30);
- }
- $keep_alive = max(60, $keep_alive);
- $this->session->set_keep_alive($keep_alive);
- }
-
- $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
- $this->session->set_ip_check($this->config->get('ip_check'));
- }
-
-
- /**
- * Garbage collector function for temp files.
- * Remove temp files older than two days
- */
- public function temp_gc()
- {
- $tmp = unslashify($this->config->get('temp_dir'));
- $expire = time() - 172800; // expire in 48 hours
-
- if ($tmp && ($dir = opendir($tmp))) {
- while (($fname = readdir($dir)) !== false) {
- if ($fname{0} == '.') {
- continue;
- }
-
- if (filemtime($tmp.'/'.$fname) < $expire) {
- @unlink($tmp.'/'.$fname);
- }
- }
-
- closedir($dir);
- }
- }
-
-
- /**
- * Garbage collector for cache entries.
- * Set flag to expunge caches on shutdown
- */
- public function cache_gc()
- {
- // because this gc function is called before storage is initialized,
- // we just set a flag to expunge storage cache on shutdown.
- $this->expunge_cache = true;
- }
-
-
- /**
- * Get localized text in the desired language
- *
- * @param mixed $attrib Named parameters array or label name
- * @param string $domain Label domain (plugin) name
- *
- * @return string Localized text
- */
- public function gettext($attrib, $domain=null)
- {
- // load localization files if not done yet
- if (empty($this->texts)) {
- $this->load_language();
- }
-
- // extract attributes
- if (is_string($attrib)) {
- $attrib = array('name' => $attrib);
- }
-
- $name = $attrib['name'] ? $attrib['name'] : '';
-
- // attrib contain text values: use them from now
- if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
- $this->texts[$name] = $setval;
- }
-
- // check for text with domain
- if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
- }
- // text does not exist
- else if (!($text = $this->texts[$name])) {
- return "[$name]";
- }
-
- // replace vars in text
- if (is_array($attrib['vars'])) {
- foreach ($attrib['vars'] as $var_key => $var_value) {
- $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
- }
- }
-
- // format output
- if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) {
- return ucfirst($text);
- }
- else if ($attrib['uppercase']) {
- return mb_strtoupper($text);
- }
- else if ($attrib['lowercase']) {
- return mb_strtolower($text);
- }
-
- return strtr($text, array('\n' => "\n"));
- }
-
-
- /**
- * Check if the given text label exists
- *
- * @param string $name Label name
- * @param string $domain Label domain (plugin) name or '*' for all domains
- * @param string $ref_domain Sets domain name if label is found
- *
- * @return boolean True if text exists (either in the current language or in en_US)
- */
- public function text_exists($name, $domain = null, &$ref_domain = null)
- {
- // load localization files if not done yet
- if (empty($this->texts)) {
- $this->load_language();
- }
-
- if (isset($this->texts[$name])) {
- $ref_domain = '';
- return true;
- }
-
- // any of loaded domains (plugins)
- if ($domain == '*') {
- foreach ($this->plugins->loaded_plugins() as $domain) {
- if (isset($this->texts[$domain.'.'.$name])) {
- $ref_domain = $domain;
- return true;
- }
- }
- }
- // specified domain
- else if ($domain) {
- $ref_domain = $domain;
- return isset($this->texts[$domain.'.'.$name]);
- }
-
- return false;
- }
-
-
- /**
- * Load a localization package
- *
- * @param string Language ID
- * @param array Additional text labels/messages
- */
- public function load_language($lang = null, $add = array())
- {
- $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
-
- // load localized texts
- if (empty($this->texts) || $lang != $_SESSION['language']) {
- $this->texts = array();
-
- // handle empty lines after closing PHP tag in localization files
- ob_start();
-
- // get english labels (these should be complete)
- @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
- @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
-
- if (is_array($labels))
- $this->texts = $labels;
- if (is_array($messages))
- $this->texts = array_merge($this->texts, $messages);
-
- // include user language files
- if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
- include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
- include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
-
- if (is_array($labels))
- $this->texts = array_merge($this->texts, $labels);
- if (is_array($messages))
- $this->texts = array_merge($this->texts, $messages);
- }
-
- ob_end_clean();
-
- $_SESSION['language'] = $lang;
- }
-
- // append additional texts (from plugin)
- if (is_array($add) && !empty($add)) {
- $this->texts += $add;
- }
- }
-
-
- /**
- * Check the given string and return a valid language code
- *
- * @param string Language code
- *
- * @return string Valid language code
- */
- protected function language_prop($lang)
- {
- static $rcube_languages, $rcube_language_aliases;
-
- // user HTTP_ACCEPT_LANGUAGE if no language is specified
- if (empty($lang) || $lang == 'auto') {
- $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
- $lang = str_replace('-', '_', $accept_langs[0]);
- }
-
- if (empty($rcube_languages)) {
- @include(INSTALL_PATH . 'program/localization/index.inc');
- }
-
- // check if we have an alias for that language
- if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
- $lang = $rcube_language_aliases[$lang];
- }
- // try the first two chars
- else if (!isset($rcube_languages[$lang])) {
- $short = substr($lang, 0, 2);
-
- // check if we have an alias for the short language code
- if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
- $lang = $rcube_language_aliases[$short];
- }
- // expand 'nn' to 'nn_NN'
- else if (!isset($rcube_languages[$short])) {
- $lang = $short.'_'.strtoupper($short);
- }
- }
-
- if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
- $lang = 'en_US';
- }
-
- return $lang;
- }
-
-
- /**
- * Read directory program/localization and return a list of available languages
- *
- * @return array List of available localizations
- */
- public function list_languages()
- {
- static $sa_languages = array();
-
- if (!sizeof($sa_languages)) {
- @include(INSTALL_PATH . 'program/localization/index.inc');
-
- if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
- while (($name = readdir($dh)) !== false) {
- if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) {
- continue;
- }
-
- if ($label = $rcube_languages[$name]) {
- $sa_languages[$name] = $label;
- }
- }
- closedir($dh);
- }
- }
-
- return $sa_languages;
- }
-
-
- /**
- * Encrypt using 3DES
- *
- * @param string $clear clear text input
- * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
- * @param boolean $base64 whether or not to base64_encode() the result before returning
- *
- * @return string encrypted text
- */
- public function encrypt($clear, $key = 'des_key', $base64 = true)
- {
- if (!$clear) {
- return '';
- }
-
- /*-
- * Add a single canary byte to the end of the clear text, which
- * will help find out how much of padding will need to be removed
- * upon decryption; see http://php.net/mcrypt_generic#68082
- */
- $clear = pack("a*H2", $clear, "80");
-
- if (function_exists('mcrypt_module_open') &&
- ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
- ) {
- $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
- mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
- $cipher = $iv . mcrypt_generic($td, $clear);
- mcrypt_generic_deinit($td);
- mcrypt_module_close($td);
- }
- else {
- @include_once 'des.inc';
-
- if (function_exists('des')) {
- $des_iv_size = 8;
- $iv = $this->create_iv($des_iv_size);
- $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
- }
- else {
- self::raise_error(array(
- 'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
- ), true, true);
- }
- }
-
- return $base64 ? base64_encode($cipher) : $cipher;
- }
-
-
- /**
- * Decrypt 3DES-encrypted string
- *
- * @param string $cipher encrypted text
- * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
- * @param boolean $base64 whether or not input is base64-encoded
- *
- * @return string decrypted text
- */
- public function decrypt($cipher, $key = 'des_key', $base64 = true)
- {
- if (!$cipher) {
- return '';
- }
-
- $cipher = $base64 ? base64_decode($cipher) : $cipher;
-
- if (function_exists('mcrypt_module_open') &&
- ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
- ) {
- $iv_size = mcrypt_enc_get_iv_size($td);
- $iv = substr($cipher, 0, $iv_size);
-
- // session corruption? (#1485970)
- if (strlen($iv) < $iv_size) {
- return '';
- }
-
- $cipher = substr($cipher, $iv_size);
- mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
- $clear = mdecrypt_generic($td, $cipher);
- mcrypt_generic_deinit($td);
- mcrypt_module_close($td);
- }
- else {
- @include_once 'des.inc';
-
- if (function_exists('des')) {
- $des_iv_size = 8;
- $iv = substr($cipher, 0, $des_iv_size);
- $cipher = substr($cipher, $des_iv_size);
- $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
- }
- else {
- self::raise_error(array(
- 'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
- ), true, true);
- }
- }
-
- /*-
- * Trim PHP's padding and the canary byte; see note in
- * rcube::encrypt() and http://php.net/mcrypt_generic#68082
- */
- $clear = substr(rtrim($clear, "\0"), 0, -1);
-
- return $clear;
- }
-
-
- /**
- * Generates encryption initialization vector (IV)
- *
- * @param int Vector size
- *
- * @return string Vector string
- */
- private function create_iv($size)
- {
- // mcrypt_create_iv() can be slow when system lacks entrophy
- // we'll generate IV vector manually
- $iv = '';
- for ($i = 0; $i < $size; $i++) {
- $iv .= chr(mt_rand(0, 255));
- }
-
- return $iv;
- }
-
-
- /**
- * Build a valid URL to this instance of Roundcube
- *
- * @param mixed Either a string with the action or url parameters as key-value pairs
- * @return string Valid application URL
- */
- public function url($p)
- {
- // STUB: should be overloaded by the application
- return '';
- }
-
-
- /**
- * Function to be executed in script shutdown
- * Registered with register_shutdown_function()
- */
- public function shutdown()
- {
- foreach ($this->shutdown_functions as $function) {
- call_user_func($function);
- }
-
- if (is_object($this->smtp)) {
- $this->smtp->disconnect();
- }
-
- foreach ($this->caches as $cache) {
- if (is_object($cache)) {
- $cache->close();
- }
- }
-
- if (is_object($this->storage)) {
- if ($this->expunge_cache) {
- $this->storage->expunge_cache();
- }
- $this->storage->close();
- }
- }
-
-
- /**
- * Registers shutdown function to be executed on shutdown.
- * The functions will be executed before destroying any
- * objects like smtp, imap, session, etc.
- *
- * @param callback Function callback
- */
- public function add_shutdown_function($function)
- {
- $this->shutdown_functions[] = $function;
- }
-
-
- /**
- * Construct shell command, execute it and return output as string.
- * Keywords {keyword} are replaced with arguments
- *
- * @param $cmd Format string with {keywords} to be replaced
- * @param $values (zero, one or more arrays can be passed)
- *
- * @return output of command. shell errors not detectable
- */
- public static function exec(/* $cmd, $values1 = array(), ... */)
- {
- $args = func_get_args();
- $cmd = array_shift($args);
- $values = $replacements = array();
-
- // merge values into one array
- foreach ($args as $arg) {
- $values += (array)$arg;
- }
-
- preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
- foreach ($matches as $tags) {
- list(, $tag, $option, $key) = $tags;
- $parts = array();
-
- if ($option) {
- foreach ((array)$values["-$key"] as $key => $value) {
- if ($value === true || $value === false || $value === null) {
- $parts[] = $value ? $key : "";
- }
- else {
- foreach ((array)$value as $val) {
- $parts[] = "$key " . escapeshellarg($val);
- }
- }
- }
- }
- else {
- foreach ((array)$values[$key] as $value) {
- $parts[] = escapeshellarg($value);
- }
- }
-
- $replacements[$tag] = join(" ", $parts);
- }
-
- // use strtr behaviour of going through source string once
- $cmd = strtr($cmd, $replacements);
-
- return (string)shell_exec($cmd);
- }
-
-
- /**
- * Print or write debug messages
- *
- * @param mixed Debug message or data
- */
- public static function console()
- {
- $args = func_get_args();
-
- if (class_exists('rcube', false)) {
- $rcube = self::get_instance();
- $plugin = $rcube->plugins->exec_hook('console', array('args' => $args));
- if ($plugin['abort']) {
- return;
- }
- $args = $plugin['args'];
- }
-
- $msg = array();
- foreach ($args as $arg) {
- $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
- }
-
- self::write_log('console', join(";\n", $msg));
- }
-
-
- /**
- * Append a line to a logfile in the logs directory.
- * Date will be added automatically to the line.
- *
- * @param $name name of log file
- * @param line Line to append
- */
- public static function write_log($name, $line)
- {
- if (!is_string($line)) {
- $line = var_export($line, true);
- }
-
- $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null;
- $log_driver = self::$instance ? self::$instance->config->get('log_driver') : null;
-
- if (empty($date_format)) {
- $date_format = 'd-M-Y H:i:s O';
- }
-
- $date = date($date_format);
-
- // trigger logging hook
- if (is_object(self::$instance) && is_object(self::$instance->plugins)) {
- $log = self::$instance->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
- $name = $log['name'];
- $line = $log['line'];
- $date = $log['date'];
- if ($log['abort'])
- return true;
- }
-
- if ($log_driver == 'syslog') {
- $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
- syslog($prio, $line);
- return true;
- }
-
- // log_driver == 'file' is assumed here
-
- $line = sprintf("[%s]: %s\n", $date, $line);
- $log_dir = self::$instance ? self::$instance->config->get('log_dir') : null;
-
- if (empty($log_dir)) {
- $log_dir = INSTALL_PATH . 'logs';
- }
-
- // try to open specific log file for writing
- $logfile = $log_dir.'/'.$name;
-
- if ($fp = @fopen($logfile, 'a')) {
- fwrite($fp, $line);
- fflush($fp);
- fclose($fp);
- return true;
- }
-
- trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
- return false;
- }
-
-
- /**
- * Throw system error (and show error page).
- *
- * @param array Named parameters
- * - code: Error code (required)
- * - type: Error type [php|db|imap|javascript] (required)
- * - message: Error message
- * - file: File where error occured
- * - line: Line where error occured
- * @param boolean True to log the error
- * @param boolean Terminate script execution
- */
- public static function raise_error($arg = array(), $log = false, $terminate = false)
- {
- // handle PHP exceptions
- if (is_object($arg) && is_a($arg, 'Exception')) {
- $err = array(
- 'type' => 'php',
- 'code' => $arg->getCode(),
- 'line' => $arg->getLine(),
- 'file' => $arg->getFile(),
- 'message' => $arg->getMessage(),
- );
- $arg = $err;
- }
-
- // installer
- if (class_exists('rcube_install', false)) {
- $rci = rcube_install::get_instance();
- $rci->raise_error($arg);
- return;
- }
-
- if (($log || $terminate) && $arg['type'] && $arg['message']) {
- $arg['fatal'] = $terminate;
- self::log_bug($arg);
- }
-
- // display error page and terminate script
- if ($terminate && is_object(self::$instance->output)) {
- self::$instance->output->raise_error($arg['code'], $arg['message']);
- }
- }
-
-
- /**
- * Report error according to configured debug_level
- *
- * @param array Named parameters
- * @see self::raise_error()
- */
- public static function log_bug($arg_arr)
- {
- $program = strtoupper($arg_arr['type']);
- $level = self::get_instance()->config->get('debug_level');
-
- // disable errors for ajax requests, write to log instead (#1487831)
- if (($level & 4) && !empty($_REQUEST['_remote'])) {
- $level = ($level ^ 4) | 1;
- }
-
- // write error to local log file
- if (($level & 1) || !empty($arg_arr['fatal'])) {
- if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- $post_query = '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']);
- }
- else {
- $post_query = '';
- }
-
- $log_entry = sprintf("%s Error: %s%s (%s %s)",
- $program,
- $arg_arr['message'],
- $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
- $_SERVER['REQUEST_METHOD'],
- $_SERVER['REQUEST_URI'] . $post_query);
-
- if (!self::write_log('errors', $log_entry)) {
- // send error to PHPs error handler if write_log didn't succeed
- trigger_error($arg_arr['message']);
- }
- }
-
- // report the bug to the global bug reporting system
- if ($level & 2) {
- // TODO: Send error via HTTP
- }
-
- // show error if debug_mode is on
- if ($level & 4) {
- print "<b>$program Error";
-
- if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) {
- print " in $arg_arr[file] ($arg_arr[line])";
- }
-
- print ':</b>&nbsp;';
- print nl2br($arg_arr['message']);
- print '<br />';
- flush();
- }
- }
-
-
- /**
- * Returns current time (with microseconds).
- *
- * @return float Current time in seconds since the Unix
- */
- public static function timer()
- {
- return microtime(true);
- }
-
-
- /**
- * Logs time difference according to provided timer
- *
- * @param float $timer Timer (self::timer() result)
- * @param string $label Log line prefix
- * @param string $dest Log file name
- *
- * @see self::timer()
- */
- public static function print_timer($timer, $label = 'Timer', $dest = 'console')
- {
- static $print_count = 0;
-
- $print_count++;
- $now = self::timer();
- $diff = $now - $timer;
-
- if (empty($label)) {
- $label = 'Timer '.$print_count;
- }
-
- self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
- }
-
-
- /**
- * Getter for logged user ID.
- *
- * @return mixed User identifier
- */
- public function get_user_id()
- {
- if (is_object($this->user)) {
- return $this->user->ID;
- }
- else if (isset($_SESSION['user_id'])) {
- return $_SESSION['user_id'];
- }
-
- return null;
- }
-
-
- /**
- * Getter for logged user name.
- *
- * @return string User name
- */
- public function get_user_name()
- {
- if (is_object($this->user)) {
- return $this->user->get_username();
- }
-
- return null;
- }
-}
-
-
-/**
- * Lightweight plugin API class serving as a dummy if plugins are not enabled
- *
- * @package Core
- */
-class rcube_dummy_plugin_api
-{
- /**
- * Triggers a plugin hook.
- * @see rcube_plugin_api::exec_hook()
- */
- public function exec_hook($hook, $args = array())
- {
- return $args;
- }
-}
diff --git a/program/include/rcube_addressbook.php b/program/include/rcube_addressbook.php
deleted file mode 100644
index 892ae263a..000000000
--- a/program/include/rcube_addressbook.php
+++ /dev/null
@@ -1,532 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_addressbook.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2012, The Roundcube Dev Team |
- | |
- | Licensed under the GNU General Public License version 3 or |
- | any later version with exceptions for skins & plugins. |
- | 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 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;
-
- /** public properties (mandatory) */
- public $primary_key;
- public $groups = false;
- 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 $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 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 List of cols to show
- * @param int 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 List of fields to search in
- * @param string Search value
- * @param int Matching mode:
- * 0 - partial (*abc*),
- * 1 - strict (=),
- * 2 - prefix (abc*)
- * @param boolean True if results are requested, False if count only
- * @param boolean True to skip the count query (select only)
- * @param array 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 record identifier(s)
- * @param boolean True to return record as associative array, otherwise a result set is returned
- *
- * @return mixed Result object with all record fields or False if not found
- */
- abstract function get_record($id, $assoc=false);
-
- /**
- * Returns the last error occured (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 Error type (one of this class' error constants)
- * @param string 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 number to list
- * @access public
- */
- function set_page($page)
- {
- $this->list_page = (int)$page;
- }
-
- /**
- * Set internal page size
- *
- * @param number Number of messages to display on one page
- * @access public
- */
- 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 Assoziative array with data to save
- * @param boolean Attempt to fix/complete record automatically
- * @return boolean True if input is valid, False if not.
- */
- public function validate(&$save_data, $autofix = false)
- {
- $rcmail = rcmail::get_instance();
-
- // 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 = $rcmail->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
- $this->set_error(self::ERROR_VALIDATE, $error);
- return false;
- }
- }
- }
-
- return true;
- }
-
-
- /**
- * Create a new contact record
- *
- * @param array Assoziative 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 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 object rcube_result_set Recordset to insert
- * @param boolean 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 Record identifier
- * @param array Assoziative 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 boolean True on success, False on error
- */
- function update($id, $save_cols)
- {
- /* empty for read-only address books */
- }
-
- /**
- * Mark one or more contact records as deleted
- *
- * @param array Record identifiers
- * @param bool 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 Record identifiers
- */
- function undelete($ids)
- {
- /* empty for read-only address books */
- }
-
- /**
- * Mark all records in database as deleted
- */
- function delete_all()
- {
- /* empty for read-only address books */
- }
-
- /**
- * Setter for the current group
- * (empty, has to be re-implemented by extending class)
- */
- function set_group($gid) { }
-
- /**
- * List all active contact groups of this source
- *
- * @param string Optional search string to match group name
- * @return array Indexed list of contact groups, each a hash array
- */
- function list_groups($search = null)
- {
- /* empty for address books don't supporting groups */
- return array();
- }
-
- /**
- * Get group properties such as name and email address(es)
- *
- * @param string 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 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 identifier
- * @return boolean True on success, false if no data was changed
- */
- function delete_group($gid)
- {
- /* empty for address books don't supporting groups */
- return false;
- }
-
- /**
- * Rename a specific contact group
- *
- * @param string Group identifier
- * @param string New name to set for this group
- * @param string New group identifier (if changed, otherwise don't set)
- * @return boolean New name on success, false if no data was changed
- */
- function rename_group($gid, $newname, &$newid)
- {
- /* empty for address books don't supporting groups */
- return false;
- }
-
- /**
- * Add the given contact records the a certain group
- *
- * @param string Group identifier
- * @param array 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 identifier
- * @param array 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 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 name
- * @param array Record data array as used for saving
- * @param boolean True to return one array with all values, False for hash array with values grouped by type
- * @return array List of column values
- */
- 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($f, $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 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 Hash array with contact data as key-value pairs
- * @param bool 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 = rcmail::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 = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
-
- 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 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 = rcmail::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
- $fn = !empty($contact['name']) ? $contact['name'] : join(' ', array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']));
-
- $fn = trim($fn, ', ');
-
- // fallback to display name
- if (empty($fn) && $contact['name'])
- $fn = $contact['name'];
-
- // fallback to email address
- $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
- if (empty($fn) && $email)
- return $email;
-
- return $fn;
- }
-
-}
-
diff --git a/program/include/rcube_base_replacer.php b/program/include/rcube_base_replacer.php
deleted file mode 100644
index 4ec367552..000000000
--- a/program/include/rcube_base_replacer.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_base_replacer.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 Core
- * @author Thomas Bruederli <roundcube@gmail.com>
- */
-class rcube_base_replacer
-{
- private $base_url;
-
-
- public function __construct($base)
- {
- $this->base_url = $base;
- }
-
-
- public function callback($matches)
- {
- return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
- }
-
-
- public function replace($body)
- {
- return preg_replace_callback(array(
- '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui',
- '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui',
- ),
- 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)
- {
- $host_url = $base_url;
- $abs_path = $path;
-
- // 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;
- }
-
- // 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)) {
- foreach ($matches as $a_match) {
- if (strrpos($base_url, '/')) {
- $base_url = substr($base_url, 0, strrpos($base_url, '/'));
- }
- $path = substr($path, 3);
- }
- }
-
- $abs_path = $base_url.'/'.$path;
- }
-
- return $abs_path;
- }
-}
diff --git a/program/include/rcube_browser.php b/program/include/rcube_browser.php
deleted file mode 100644
index 7cfae709d..000000000
--- a/program/include/rcube_browser.php
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_browser.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2007-2009, 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 Core
- */
-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->opera = strpos($HTTP_USER_AGENT, 'opera') !== false;
- $this->ns4 = strpos($HTTP_USER_AGENT, 'mozilla/4') !== false && strpos($HTTP_USER_AGENT, 'msie') === false;
- $this->ns = ($this->ns4 || strpos($HTTP_USER_AGENT, 'netscape') !== false);
- $this->ie = !$this->opera && strpos($HTTP_USER_AGENT, 'compatible; msie') !== false;
- $this->khtml = strpos($HTTP_USER_AGENT, 'khtml') !== false;
- $this->mz = !$this->ie && !$this->khtml && strpos($HTTP_USER_AGENT, 'mozilla/5') !== false;
- $this->chrome = strpos($HTTP_USER_AGENT, 'chrome') !== false;
- $this->safari = !$this->chrome && ($this->khtml || strpos($HTTP_USER_AGENT, 'safari') !== false);
-
- if ($this->ns || $this->chrome) {
- $test = preg_match('/(mozilla|chrome)\/([0-9.]+)/', $HTTP_USER_AGENT, $regs);
- $this->ver = $test ? (float)$regs[2] : 0;
- }
- else if ($this->mz) {
- $test = preg_match('/rv:([0-9.]+)/', $HTTP_USER_AGENT, $regs);
- $this->ver = $test ? (float)$regs[1] : 0;
- }
- else if ($this->ie || $this->opera) {
- $test = preg_match('/(msie|opera) ([0-9.]+)/', $HTTP_USER_AGENT, $regs);
- $this->ver = $test ? (float)$regs[2] : 0;
- }
-
- 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) ? true : false;
- $this->imgdata = !$this->ie;
- }
-}
-
diff --git a/program/include/rcube_cache.php b/program/include/rcube_cache.php
deleted file mode 100644
index 4e60deaff..000000000
--- a/program/include/rcube_cache.php
+++ /dev/null
@@ -1,559 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_cache.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2011, The Roundcube Dev Team |
- | Copyright (C) 2011, Kolab Systems AG |
- | |
- | 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 Cache
- * @author Thomas Bruederli <roundcube@gmail.com>
- * @author Aleksander Machniak <alec@alec.pl>
- * @version 1.1
- */
-class rcube_cache
-{
- /**
- * Instance of database handler
- *
- * @var rcube_db|Memcache|bool
- */
- private $db;
- private $type;
- private $userid;
- private $prefix;
- private $ttl;
- private $packed;
- private $index;
- private $cache = array();
- private $cache_changes = array();
- private $cache_sums = array();
-
-
- /**
- * 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();
- }
- else if ($type == 'apc') {
- $this->type = 'apc';
- $this->db = function_exists('apc_exists'); // APC 3.1.4 required
- }
- else {
- $this->type = 'db';
- $this->db = $rcube->get_dbh();
- }
-
- // 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_changed = true;
- $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->packed ? serialize($data) : $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_changed = false;
- $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->db->query(
- "DELETE FROM ".$this->db->table_name('cache').
- " WHERE user_id = ?".
- " AND cache_key LIKE ?".
- " AND " . $this->db->unixtimestamp('created')." < ?",
- $this->userid,
- $this->prefix.'.%',
- time() - $this->ttl);
- }
- }
-
-
- /**
- * Writes the cache back to the DB.
- */
- function close()
- {
- if (!$this->cache_changed) {
- return;
- }
-
- 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->packed ? serialize($data) : $data;
-
- if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
- $this->write_record($key, $data);
- }
- }
- }
-
- $this->write_index();
- }
-
-
- /**
- * 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') {
- if ($this->type == 'memcache') {
- $data = $this->db->get($this->ckey($key));
- }
- else if ($this->type == 'apc') {
- $data = apc_fetch($this->ckey($key));
- }
-
- if ($data) {
- $md5sum = md5($data);
- $data = $this->packed ? unserialize($data) : $data;
-
- if ($nostore) {
- return $data;
- }
-
- $this->cache_sums[$key] = $md5sum;
- $this->cache[$key] = $data;
- }
- else {
- $this->cache[$key] = null;
- }
- }
- else {
- $sql_result = $this->db->limitquery(
- "SELECT data, cache_key".
- " FROM ".$this->db->table_name('cache').
- " WHERE user_id = ?".
- " AND cache_key = ?".
- // for better performance we allow more records for one key
- // get the newer one
- " ORDER BY created DESC",
- 0, 1, $this->userid, $this->prefix.'.'.$key);
-
- if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $key = substr($sql_arr['cache_key'], strlen($this->prefix)+1);
- $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null;
- if ($sql_arr['data']) {
- $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data'];
- }
-
- 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 mxied $data Serialized cache data
- *
- * @param boolean True on success, False on failure
- */
- private function write_record($key, $data)
- {
- if (!$this->db) {
- return false;
- }
-
- if ($this->type == 'memcache' || $this->type == 'apc') {
- return $this->add_record($this->ckey($key), $data);
- }
-
- $key_exists = array_key_exists($key, $this->cache_sums);
- $key = $this->prefix . '.' . $key;
-
- // Remove NULL rows (here we don't need to check if the record exist)
- if ($data == 'N;') {
- $this->db->query(
- "DELETE FROM ".$this->db->table_name('cache').
- " WHERE user_id = ?".
- " AND cache_key = ?",
- $this->userid, $key);
-
- return true;
- }
-
- // update existing cache record
- if ($key_exists) {
- $result = $this->db->query(
- "UPDATE ".$this->db->table_name('cache').
- " SET created = ". $this->db->now().", data = ?".
- " WHERE user_id = ?".
- " AND cache_key = ?",
- $data, $this->userid, $key);
- }
- // add new cache record
- else {
- // for better performance we allow more records for one key
- // so, no need to check if record exist (see rcube_cache::read_record())
- $result = $this->db->query(
- "INSERT INTO ".$this->db->table_name('cache').
- " (created, user_id, cache_key, data)".
- " VALUES (".$this->db->now().", ?, ?, ?)",
- $this->userid, $key, $data);
- }
-
- return $this->db->affected_rows($result);
- }
-
-
- /**
- * 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($key, false);
- }
- $this->index = array();
- }
- // Remove keys by name prefix
- else if ($prefix_mode) {
- foreach ($this->index as $k) {
- if (strpos($k, $key) === 0) {
- $this->delete_record($k);
- }
- }
- }
- // Remove one key by name
- else {
- $this->delete_record($key);
- }
-
- 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->db->table_name('cache').
- " WHERE user_id = ?" . $where,
- $this->userid);
- }
-
-
- /**
- * Adds entry into memcache/apc DB.
- *
- * @param string $key Cache key name
- * @param mxied $data Serialized cache data
- * @param bollean $index Enables immediate index update
- *
- * @param boolean True on success, False on failure
- */
- private function add_record($key, $data, $index=false)
- {
- 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);
- }
-
- // Update index
- if ($index && $result) {
- $this->load_index();
-
- if (array_search($key, $this->index) === false) {
- $this->index[] = $key;
- $data = serialize($this->index);
- $this->add_record($this->ikey(), $data);
- }
- }
-
- return $result;
- }
-
-
- /**
- * Deletes entry from memcache/apc DB.
- */
- private function delete_record($key, $index=true)
- {
- if ($this->type == 'memcache') {
- // #1488592: use 2nd argument
- $this->db->delete($this->ckey($key), 0);
- }
- else {
- apc_delete($this->ckey($key));
- }
-
- if ($index) {
- if (($idx = array_search($key, $this->index)) !== false) {
- unset($this->index[$idx]);
- }
- }
- }
-
-
- /**
- * Writes the index entry into memcache/apc DB.
- */
- private function write_index()
- {
- if (!$this->db) {
- return;
- }
-
- if ($this->type == 'db') {
- return;
- }
-
- $this->load_index();
-
- // Make sure index contains new keys
- foreach ($this->cache as $key => $value) {
- if ($value !== null) {
- if (array_search($key, $this->index) === false) {
- $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) {
- 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);
- }
-
- $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');
- }
-}
diff --git a/program/include/rcube_charset.php b/program/include/rcube_charset.php
deleted file mode 100644
index ff4c2bbce..000000000
--- a/program/include/rcube_charset.php
+++ /dev/null
@@ -1,763 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_charset.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | 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 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 Level of the error
- * @param string 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] ? $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 Input string
- * @param string Suspected charset of the input string
- * @param string Target charset to convert to; defaults to RCMAIL_CHARSET
- *
- * @return string Converted string
- */
- public static function convert($str, $from, $to = null)
- {
- static $iconv_options = null;
- static $mbstring_list = null;
- static $mbstring_sch = null;
- static $conv = null;
-
- $to = empty($to) ? RCMAIL_CHARSET : $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 = '';
- }
- }
- }
-
- // convert charset using iconv module
- if ($iconv_options !== null && $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 {
- $_iconv = iconv($from, $to . $iconv_options, $str);
- } catch (ErrorException $e) {
- $_iconv = false;
- }
- restore_error_handler();
-
- if ($_iconv !== false) {
- return $_iconv;
- }
- }
-
- if ($mbstring_list === null) {
- if (extension_loaded('mbstring')) {
- $mbstring_sch = mb_substitute_character();
- $mbstring_list = mb_list_encodings();
- $mbstring_list = array_map('strtoupper', $mbstring_list);
- }
- }
-
- // convert charset using mbstring module
- if ($mbstring_list !== null) {
- $aliases['WINDOWS-1257'] = 'ISO-8859-13';
- // it happens that mbstring supports ASCII but not US-ASCII
- if (($from == 'US-ASCII' || $to == 'US-ASCII') && !in_array('US-ASCII', $mbstring_list)) {
- $aliases['US-ASCII'] = 'ASCII';
- }
-
- $mb_from = $aliases[$from] ? $aliases[$from] : $from;
- $mb_to = $aliases[$to] ? $aliases[$to] : $to;
-
- // return if encoding found, string matches encoding and convert succeeded
- if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
- if (mb_check_encoding($str, $mb_from)) {
- // Do the same as //IGNORE with iconv
- mb_substitute_character('none');
- $out = mb_convert_encoding($str, $mb_to, $mb_from);
- mb_substitute_character($mbstring_sch);
-
- if ($out !== false) {
- return $out;
- }
- }
- }
- }
-
- // convert charset using bundled classes/functions
- if ($to == 'UTF-8') {
- if ($from == 'UTF7-IMAP') {
- if ($_str = self::utf7imap_to_utf8($str)) {
- return $_str;
- }
- }
- else if ($from == 'UTF-7') {
- if ($_str = self::utf7_to_utf8($str)) {
- return $_str;
- }
- }
- else if ($from == 'ISO-8859-1' && function_exists('utf8_encode')) {
- return utf8_encode($str);
- }
- else if (class_exists('utf8')) {
- if (!$conv) {
- $conv = new utf8($from);
- }
- else {
- $conv->loadCharset($from);
- }
-
- if ($_str = $conv->strToUtf8($str)) {
- return $_str;
- }
- }
- }
-
- // 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 ($_str = self::utf8_to_utf7imap($str)) {
- return $_str;
- }
- }
- else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
- return utf8_decode($str);
- }
- else if (class_exists('utf8')) {
- if (!$conv) {
- $conv = new utf8($to);
- }
- else {
- $conv->loadCharset($from);
- }
-
- if ($_str = $conv->strToUtf8($str)) {
- return $_str;
- }
- }
- }
-
- // return original string
- return $str;
- }
-
-
- /**
- * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
- *
- * @param string 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 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.
- *
- * @return string Charset name
- */
- public static function detect($string, $failover='')
- {
- if (!function_exists('mb_detect_encoding')) {
- return $failover;
- }
-
- // FIXME: the order is important, because sometimes
- // iso string is detected as euc-jp and etc.
- $enc = array(
- 'UTF-8', 'SJIS', 'BIG5', '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',
- 'ISO-2022-KR', 'ISO-2022-JP'
- );
-
- $result = mb_detect_encoding($string, join(',', $enc));
-
- return $result ? $result : $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')) {
- if (($res = mb_convert_encoding($input, 'UTF-8', 'UTF-8')) !== 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 (or second) byte of multibyte sequence
- }
- else if ($ord >= 0xC0) {
- if (strlen($seq) > 1) {
- $out .= preg_match($regexp, $seq) ? $seq : '';
- $seq = '';
- }
- else if ($seq && ord($seq) < 0xC0) {
- $seq = '';
- }
- $seq .= $chr;
- // next byte of multibyte sequence
- }
- else if ($seq) {
- $seq .= $chr;
- }
- }
-
- if ($seq) {
- $out .= preg_match($regexp, $seq) ? $seq : '';
- }
-
- return $out;
- }
-
-}
diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php
deleted file mode 100644
index b9fd95578..000000000
--- a/program/include/rcube_config.php
+++ /dev/null
@@ -1,418 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_config.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: |
- | Class to read configuration settings |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-*/
-
-/**
- * Configuration class for Roundcube
- *
- * @package Core
- */
-class rcube_config
-{
- const DEFAULT_SKIN = 'larry';
-
- private $prop = array();
- private $errors = array();
- private $userprefs = array();
-
- /**
- * Renamed options
- *
- * @var array
- */
- private $legacy_props = array(
- // new name => old name
- 'default_folders' => 'default_imap_folders',
- 'mail_pagesize' => 'pagesize',
- 'addressbook_pagesize' => 'pagesize',
- 'reply_mode' => 'top_posting',
- );
-
-
- /**
- * Object constructor
- */
- public function __construct()
- {
- $this->load();
-
- // Defaults, that we do not require you to configure,
- // but contain information that is used in various
- // locations in the code:
- $this->set('contactlist_fields', array('name', 'firstname', 'surname', 'email'));
- }
-
-
- /**
- * Load config from local config file
- *
- * @todo Remove global $CONFIG
- */
- private function load()
- {
- // load main config file
- if (!$this->load_from_file(RCMAIL_CONFIG_DIR . '/main.inc.php'))
- $this->errors[] = 'main.inc.php was not found.';
-
- // load database config
- if (!$this->load_from_file(RCMAIL_CONFIG_DIR . '/db.inc.php'))
- $this->errors[] = 'db.inc.php was not found.';
-
- // 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
- $this->prop['log_dir'] = $this->prop['log_dir'] ? realpath(unslashify($this->prop['log_dir'])) : INSTALL_PATH . 'logs';
- $this->prop['temp_dir'] = $this->prop['temp_dir'] ? realpath(unslashify($this->prop['temp_dir'])) : INSTALL_PATH . 'temp';
-
- // 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], RCMAIL_CHARSET, 'UTF7-IMAP');
-
- if (!empty($this->prop['default_folders']))
- foreach ($this->prop['default_folders'] as $n => $folder)
- $this->prop['default_folders'][$n] = rcube_charset::convert($folder, RCMAIL_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)));
-
- // set timezone auto settings values
- if ($this->prop['timezone'] == 'auto') {
- $this->prop['_timezone_value'] = $this->client_timezone();
- }
- else if (is_numeric($this->prop['timezone']) && ($tz = timezone_name_from_abbr("", $this->prop['timezone'] * 3600, 0))) {
- $this->prop['timezone'] = $tz;
- }
- else if (empty($this->prop['timezone'])) {
- $this->prop['timezone'] = 'UTC';
- }
-
- // remove deprecated properties
- unset($this->prop['dst_active']);
-
- // 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()
- {
- $fname = null;
-
- if (is_array($this->prop['include_host_config'])) {
- $fname = $this->prop['include_host_config'][$_SERVER['HTTP_HOST']];
- }
- else if (!empty($this->prop['include_host_config'])) {
- $fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $_SERVER['HTTP_HOST']) . '.inc.php';
- }
-
- if ($fname) {
- $this->load_from_file(RCMAIL_CONFIG_DIR . '/' . $fname);
- }
- }
-
-
- /**
- * Read configuration from a file
- * and merge with the already stored config values
- *
- * @param string $fpath Full path to the config file to be loaded
- * @return booelan True on success, false on failure
- */
- public function load_from_file($fpath)
- {
- if (is_file($fpath) && is_readable($fpath)) {
- // use output buffering, we don't need any output here
- ob_start();
- include($fpath);
- ob_end_clean();
-
- if (is_array($rcmail_config)) {
- $this->prop = array_merge($this->prop, $rcmail_config, $this->userprefs);
- return true;
- }
- }
-
- return false;
- }
-
-
- /**
- * 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 if (isset($this->legacy_props[$name])) {
- return $this->get($this->legacy_props[$name], $def);
- }
- else {
- $result = $def;
- }
-
- $rcube = rcube::get_instance();
-
- if ($name == 'timezone' && isset($this->prop['_timezone_value']))
- $result = $this->prop['_timezone_value'];
-
- $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)
- {
- $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)
- {
- // 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]);
- }
- }
-
- // convert user's timezone into the new format
- if (is_numeric($prefs['timezone']) && ($tz = timezone_name_from_abbr('', $prefs['timezone'] * 3600, 0))) {
- $prefs['timezone'] = $tz;
- }
-
- // larry is the new default skin :-)
- if ($prefs['skin'] == 'default') {
- $prefs['skin'] = self::DEFAULT_SKIN;
- }
-
- $this->userprefs = $prefs;
- $this->prop = array_merge($this->prop, $prefs);
-
- // override timezone settings with client values
- if ($this->prop['timezone'] == 'auto') {
- $this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value'];
- }
- else if (isset($this->prop['_timezone_value']))
- unset($this->prop['_timezone_value']);
- }
-
-
- /**
- * Getter for all config options
- *
- * @return array Hash array containg all config properties
- */
- public function all()
- {
- return $this->prop;
- }
-
- /**
- * 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)) {
- rcube::raise_error(array(
- 'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Request for unconfigured crypto key \"$key\""
- ), true, true);
- }
-
- $key = $this->prop[$key];
-
- // Bomb out if the configured key is not exactly 24 bytes long
- if (strlen($key) != 24) {
- rcube::raise_error(array(
- 'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
- ), true, true);
- }
-
- return $key;
- }
-
-
- /**
- * Try to autodetect operating system and find the correct line endings
- *
- * @return string The appropriate mail header delimiter
- */
- 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()
- {
- return isset($_SESSION['timezone']) && ($ctz = timezone_name_from_abbr("", $_SESSION['timezone'] * 3600, 0)) ? $ctz : date_default_timezone_get();
- }
-
-}
diff --git a/program/include/rcube_contacts.php b/program/include/rcube_contacts.php
deleted file mode 100644
index 534a65cb9..000000000
--- a/program/include/rcube_contacts.php
+++ /dev/null
@@ -1,1001 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_contacts.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2012, The Roundcube Dev Team |
- | |
- | Licensed under the GNU General Public License version 3 or |
- | any later version with exceptions for skins & plugins. |
- | See the README file for a full license statement. |
- | |
- | PURPOSE: |
- | Interface to the local address book database |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Model class for the local address book database
- *
- * @package Addressbook
- */
-class rcube_contacts extends rcube_addressbook
-{
- // protected for backward compat. with some plugins
- protected $db_name = 'contacts';
- protected $db_groups = 'contactgroups';
- protected $db_groupmembers = 'contactgroupmembers';
- protected $vcard_fieldmap = array();
-
- /**
- * Store database connection.
- *
- * @var rcube_db
- */
- private $db = null;
- private $user_id = 0;
- private $filter = null;
- private $result = null;
- private $cache;
- private $table_cols = array('name', 'email', 'firstname', 'surname');
- private $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'nickname',
- 'jobtitle', 'organization', 'department', 'maidenname', 'email', 'phone',
- 'address', 'street', 'locality', 'zipcode', 'region', 'country', 'website', 'im', 'notes');
-
- // public properties
- public $primary_key = 'contact_id';
- public $name;
- public $readonly = false;
- public $groups = true;
- public $undelete = true;
- public $list_page = 1;
- public $page_size = 10;
- public $group_id = 0;
- public $ready = false;
- public $coltypes = array('name', 'firstname', 'surname', 'middlename', 'prefix', 'suffix', 'nickname',
- 'jobtitle', 'organization', 'department', 'assistant', 'manager',
- 'gender', 'maidenname', 'spouse', 'email', 'phone', 'address',
- 'birthday', 'anniversary', 'website', 'im', 'notes', 'photo');
-
- const SEPARATOR = ',';
-
-
- /**
- * Object constructor
- *
- * @param object Instance of the rcube_db class
- * @param integer User-ID
- */
- function __construct($dbconn, $user)
- {
- $this->db = $dbconn;
- $this->user_id = $user;
- $this->ready = $this->db && !$this->db->is_error();
- }
-
-
- /**
- * Returns addressbook name
- */
- function get_name()
- {
- return $this->name;
- }
-
-
- /**
- * Save a search string for future listings
- *
- * @param string SQL params to use in listing method
- */
- function set_search_set($filter)
- {
- $this->filter = $filter;
- $this->cache = null;
- }
-
-
- /**
- * Getter for saved search properties
- *
- * @return mixed Search properties used by this class
- */
- function get_search_set()
- {
- return $this->filter;
- }
-
-
- /**
- * Setter for the current group
- * (empty, has to be re-implemented by extending class)
- */
- function set_group($gid)
- {
- $this->group_id = $gid;
- $this->cache = null;
- }
-
-
- /**
- * Reset all saved results and search parameters
- */
- function reset()
- {
- $this->result = null;
- $this->filter = null;
- $this->cache = null;
- }
-
-
- /**
- * List all active contact groups of this source
- *
- * @param string Search string to match group name
- * @return array Indexed list of contact groups, each a hash array
- */
- function list_groups($search = null)
- {
- $results = array();
-
- if (!$this->groups)
- return $results;
-
- $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : '';
-
- $sql_result = $this->db->query(
- "SELECT * FROM ".$this->db->table_name($this->db_groups).
- " WHERE del<>1".
- " AND user_id=?".
- $sql_filter.
- " ORDER BY name",
- $this->user_id);
-
- while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
- $sql_arr['ID'] = $sql_arr['contactgroup_id'];
- $results[] = $sql_arr;
- }
-
- return $results;
- }
-
-
- /**
- * Get group properties such as name and email address(es)
- *
- * @param string Group identifier
- * @return array Group properties as hash array
- */
- function get_group($group_id)
- {
- $sql_result = $this->db->query(
- "SELECT * FROM ".$this->db->table_name($this->db_groups).
- " WHERE del<>1".
- " AND contactgroup_id=?".
- " AND user_id=?",
- $group_id, $this->user_id);
-
- if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
- $sql_arr['ID'] = $sql_arr['contactgroup_id'];
- return $sql_arr;
- }
-
- return null;
- }
-
- /**
- * List the current set of contact records
- *
- * @param array List of cols to show, Null means all
- * @param int Only return this number of records, use negative values for tail
- * @param boolean True to skip the count query (select only)
- * @return array Indexed list of contact records, each a hash array
- */
- function list_records($cols=null, $subset=0, $nocount=false)
- {
- if ($nocount || $this->list_page <= 1) {
- // create dummy result, we don't need a count now
- $this->result = new rcube_result_set();
- } else {
- // count all records
- $this->result = $this->count();
- }
-
- $start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first;
- $length = $subset != 0 ? abs($subset) : $this->page_size;
-
- if ($this->group_id)
- $join = " LEFT JOIN ".$this->db->table_name($this->db_groupmembers)." AS m".
- " ON (m.contact_id = c.".$this->primary_key.")";
-
- $order_col = (in_array($this->sort_col, $this->table_cols) ? $this->sort_col : 'name');
- $order_cols = array('c.'.$order_col);
- if ($order_col == 'firstname')
- $order_cols[] = 'c.surname';
- else if ($order_col == 'surname')
- $order_cols[] = 'c.firstname';
- if ($order_col != 'name')
- $order_cols[] = 'c.name';
- $order_cols[] = 'c.email';
-
- $sql_result = $this->db->limitquery(
- "SELECT * FROM ".$this->db->table_name($this->db_name)." AS c" .
- $join .
- " WHERE c.del<>1" .
- " AND c.user_id=?" .
- ($this->group_id ? " AND m.contactgroup_id=?" : "").
- ($this->filter ? " AND (".$this->filter.")" : "") .
- " ORDER BY ". $this->db->concat($order_cols) .
- " " . $this->sort_order,
- $start_row,
- $length,
- $this->user_id,
- $this->group_id);
-
- // determine whether we have to parse the vcard or if only db cols are requested
- $read_vcard = !$cols || count(array_intersect($cols, $this->table_cols)) < count($cols);
-
- while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
- $sql_arr['ID'] = $sql_arr[$this->primary_key];
-
- if ($read_vcard)
- $sql_arr = $this->convert_db_data($sql_arr);
- else {
- $sql_arr['email'] = explode(self::SEPARATOR, $sql_arr['email']);
- $sql_arr['email'] = array_map('trim', $sql_arr['email']);
- }
-
- $this->result->add($sql_arr);
- }
-
- $cnt = count($this->result->records);
-
- // update counter
- if ($nocount)
- $this->result->count = $cnt;
- else if ($this->list_page <= 1) {
- if ($cnt < $this->page_size && $subset == 0)
- $this->result->count = $cnt;
- else if (isset($this->cache['count']))
- $this->result->count = $this->cache['count'];
- else
- $this->result->count = $this->_count();
- }
-
- return $this->result;
- }
-
-
- /**
- * Search contacts
- *
- * @param mixed $fields The field name of array of field names to search in
- * @param mixed $value Search value (or array of values when $fields is array)
- * @param int $mode Matching mode:
- * 0 - partial (*abc*),
- * 1 - strict (=),
- * 2 - prefix (abc*)
- * @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 Contact records and 'count' value
- */
- function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array())
- {
- if (!is_array($fields))
- $fields = array($fields);
- if (!is_array($required) && !empty($required))
- $required = array($required);
-
- $where = $and_where = array();
- $mode = intval($mode);
- $WS = ' ';
- $AS = self::SEPARATOR;
-
- foreach ($fields as $idx => $col) {
- // direct ID search
- if ($col == 'ID' || $col == $this->primary_key) {
- $ids = !is_array($value) ? explode(self::SEPARATOR, $value) : $value;
- $ids = $this->db->array2list($ids, 'integer');
- $where[] = 'c.' . $this->primary_key.' IN ('.$ids.')';
- continue;
- }
- // fulltext search in all fields
- else if ($col == '*') {
- $words = array();
- foreach (explode($WS, rcube_utils::normalize_string($value)) as $word) {
- switch ($mode) {
- case 1: // strict
- $words[] = '(' . $this->db->ilike('words', $word . '%')
- . ' OR ' . $this->db->ilike('words', '%' . $WS . $word . $WS . '%')
- . ' OR ' . $this->db->ilike('words', '%' . $WS . $word) . ')';
- break;
- case 2: // prefix
- $words[] = '(' . $this->db->ilike('words', $word . '%')
- . ' OR ' . $this->db->ilike('words', '%' . $WS . $word . '%') . ')';
- break;
- default: // partial
- $words[] = $this->db->ilike('words', '%' . $word . '%');
- }
- }
- $where[] = '(' . join(' AND ', $words) . ')';
- }
- else {
- $val = is_array($value) ? $value[$idx] : $value;
- // table column
- if (in_array($col, $this->table_cols)) {
- switch ($mode) {
- case 1: // strict
- $where[] = '(' . $this->db->quoteIdentifier($col) . ' = ' . $this->db->quote($val)
- . ' OR ' . $this->db->ilike($col, $val . $AS . '%')
- . ' OR ' . $this->db->ilike($col, '%' . $AS . $val . $AS . '%')
- . ' OR ' . $this->db->ilike($col, '%' . $AS . $val) . ')';
- break;
- case 2: // prefix
- $where[] = '(' . $this->db->ilike($col, $val . '%')
- . ' OR ' . $this->db->ilike($col, $AS . $val . '%') . ')';
- break;
- default: // partial
- $where[] = $this->db->ilike($col, '%' . $val . '%');
- }
- }
- // vCard field
- else {
- if (in_array($col, $this->fulltext_cols)) {
- foreach (rcube_utils::normalize_string($val, true) as $word) {
- switch ($mode) {
- case 1: // strict
- $words[] = '(' . $this->db->ilike('words', $word . $WS . '%')
- . ' OR ' . $this->db->ilike('words', '%' . $AS . $word . $WS .'%')
- . ' OR ' . $this->db->ilike('words', '%' . $AS . $word) . ')';
- break;
- case 2: // prefix
- $words[] = '(' . $this->db->ilike('words', $word . '%')
- . ' OR ' . $this->db->ilike('words', $AS . $word . '%') . ')';
- break;
- default: // partial
- $words[] = $this->db->ilike('words', '%' . $word . '%');
- }
- }
- $where[] = '(' . join(' AND ', $words) . ')';
- }
- if (is_array($value))
- $post_search[$col] = mb_strtolower($val);
- }
- }
- }
-
- foreach (array_intersect($required, $this->table_cols) as $col) {
- $and_where[] = $this->db->quoteIdentifier($col).' <> '.$this->db->quote('');
- }
-
- if (!empty($where)) {
- // use AND operator for advanced searches
- $where = join(is_array($value) ? ' AND ' : ' OR ', $where);
- }
-
- if (!empty($and_where))
- $where = ($where ? "($where) AND " : '') . join(' AND ', $and_where);
-
- // Post-searching in vCard data fields
- // we will search in all records and then build a where clause for their IDs
- if (!empty($post_search)) {
- $ids = array(0);
- // build key name regexp
- $regexp = '/^(' . implode(array_keys($post_search), '|') . ')(?:.*)$/';
- // use initial WHERE clause, to limit records number if possible
- if (!empty($where))
- $this->set_search_set($where);
-
- // count result pages
- $cnt = $this->count();
- $pages = ceil($cnt / $this->page_size);
- $scnt = count($post_search);
-
- // get (paged) result
- for ($i=0; $i<$pages; $i++) {
- $this->list_records(null, $i, true);
- while ($row = $this->result->next()) {
- $id = $row[$this->primary_key];
- $found = array();
- foreach (preg_grep($regexp, array_keys($row)) as $col) {
- $pos = strpos($col, ':');
- $colname = $pos ? substr($col, 0, $pos) : $col;
- $search = $post_search[$colname];
- foreach ((array)$row[$col] as $value) {
- // composite field, e.g. address
- foreach ((array)$value as $val) {
- $val = mb_strtolower($val);
- switch ($mode) {
- case 1:
- $got = ($val == $search);
- break;
- case 2:
- $got = ($search == substr($val, 0, strlen($search)));
- break;
- default:
- $got = (strpos($val, $search) !== false);
- break;
- }
-
- if ($got) {
- $found[$colname] = true;
- break 2;
- }
- }
- }
- }
- // all fields match
- if (count($found) >= $scnt) {
- $ids[] = $id;
- }
- }
- }
-
- // build WHERE clause
- $ids = $this->db->array2list($ids, 'integer');
- $where = 'c.' . $this->primary_key.' IN ('.$ids.')';
- // reset counter
- unset($this->cache['count']);
-
- // when we know we have an empty result
- if ($ids == '0') {
- $this->set_search_set($where);
- return ($this->result = new rcube_result_set(0, 0));
- }
- }
-
- if (!empty($where)) {
- $this->set_search_set($where);
- if ($select)
- $this->list_records(null, 0, $nocount);
- else
- $this->result = $this->count();
- }
-
- return $this->result;
- }
-
-
- /**
- * Count number of available contacts in database
- *
- * @return rcube_result_set Result object
- */
- function count()
- {
- $count = isset($this->cache['count']) ? $this->cache['count'] : $this->_count();
-
- return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
- }
-
-
- /**
- * Count number of available contacts in database
- *
- * @return int Contacts count
- */
- private function _count()
- {
- if ($this->group_id)
- $join = " LEFT JOIN ".$this->db->table_name($this->db_groupmembers)." AS m".
- " ON (m.contact_id=c.".$this->primary_key.")";
-
- // count contacts for this user
- $sql_result = $this->db->query(
- "SELECT COUNT(c.contact_id) AS rows".
- " FROM ".$this->db->table_name($this->db_name)." AS c".
- $join.
- " WHERE c.del<>1".
- " AND c.user_id=?".
- ($this->group_id ? " AND m.contactgroup_id=?" : "").
- ($this->filter ? " AND (".$this->filter.")" : ""),
- $this->user_id,
- $this->group_id
- );
-
- $sql_arr = $this->db->fetch_assoc($sql_result);
-
- $this->cache['count'] = (int) $sql_arr['rows'];
-
- return $this->cache['count'];
- }
-
-
- /**
- * Return the last result set
- *
- * @return mixed Result array or NULL if nothing selected yet
- */
- function get_result()
- {
- return $this->result;
- }
-
-
- /**
- * Get a specific contact record
- *
- * @param mixed record identifier(s)
- * @return mixed Result object with all record fields or False if not found
- */
- function get_record($id, $assoc=false)
- {
- // return cached result
- if ($this->result && ($first = $this->result->first()) && $first[$this->primary_key] == $id)
- return $assoc ? $first : $this->result;
-
- $this->db->query(
- "SELECT * FROM ".$this->db->table_name($this->db_name).
- " WHERE contact_id=?".
- " AND user_id=?".
- " AND del<>1",
- $id,
- $this->user_id
- );
-
- if ($sql_arr = $this->db->fetch_assoc()) {
- $record = $this->convert_db_data($sql_arr);
- $this->result = new rcube_result_set(1);
- $this->result->add($record);
- }
-
- return $assoc && $record ? $record : $this->result;
- }
-
-
- /**
- * Get group assignments of a specific contact record
- *
- * @param mixed Record identifier
- * @return array List of assigned groups as ID=>Name pairs
- */
- function get_record_groups($id)
- {
- $results = array();
-
- if (!$this->groups)
- return $results;
-
- $sql_result = $this->db->query(
- "SELECT cgm.contactgroup_id, cg.name FROM " . $this->db->table_name($this->db_groupmembers) . " AS cgm" .
- " LEFT JOIN " . $this->db->table_name($this->db_groups) . " AS cg ON (cgm.contactgroup_id = cg.contactgroup_id AND cg.del<>1)" .
- " WHERE cgm.contact_id=?",
- $id
- );
- while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
- $results[$sql_arr['contactgroup_id']] = $sql_arr['name'];
- }
-
- return $results;
- }
-
-
- /**
- * Check the given data before saving.
- * If input not valid, the message to display can be fetched using get_error()
- *
- * @param array Assoziative array with data to save
- * @param boolean Try to fix/complete record automatically
- * @return boolean True if input is valid, False if not.
- */
- public function validate(&$save_data, $autofix = false)
- {
- // validate e-mail addresses
- $valid = parent::validate($save_data, $autofix);
-
- // require at least one e-mail address (syntax check is already done)
- if ($valid && !array_filter($this->get_col_values('email', $save_data, true))) {
- $this->set_error(self::ERROR_VALIDATE, 'noemailwarning');
- $valid = false;
- }
-
- return $valid;
- }
-
-
- /**
- * Create a new contact record
- *
- * @param array Associative array with save data
- * @return integer|boolean The created record ID on success, False on error
- */
- function insert($save_data, $check=false)
- {
- if (!is_array($save_data))
- return false;
-
- $insert_id = $existing = false;
-
- if ($check) {
- foreach ($save_data as $col => $values) {
- if (strpos($col, 'email') === 0) {
- foreach ((array)$values as $email) {
- if ($existing = $this->search('email', $email, false, false))
- break 2;
- }
- }
- }
- }
-
- $save_data = $this->convert_save_data($save_data);
- $a_insert_cols = $a_insert_values = array();
-
- foreach ($save_data as $col => $value) {
- $a_insert_cols[] = $this->db->quoteIdentifier($col);
- $a_insert_values[] = $this->db->quote($value);
- }
-
- if (!$existing->count && !empty($a_insert_cols)) {
- $this->db->query(
- "INSERT INTO ".$this->db->table_name($this->db_name).
- " (user_id, changed, del, ".join(', ', $a_insert_cols).")".
- " VALUES (".intval($this->user_id).", ".$this->db->now().", 0, ".join(', ', $a_insert_values).")"
- );
-
- $insert_id = $this->db->insert_id($this->db_name);
- }
-
- // also add the newly created contact to the active group
- if ($insert_id && $this->group_id)
- $this->add_to_group($this->group_id, $insert_id);
-
- $this->cache = null;
-
- return $insert_id;
- }
-
-
- /**
- * Update a specific contact record
- *
- * @param mixed Record identifier
- * @param array Assoziative array with save data
- * @return boolean True on success, False on error
- */
- function update($id, $save_cols)
- {
- $updated = false;
- $write_sql = array();
- $record = $this->get_record($id, true);
- $save_cols = $this->convert_save_data($save_cols, $record);
-
- foreach ($save_cols as $col => $value) {
- $write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col), $this->db->quote($value));
- }
-
- if (!empty($write_sql)) {
- $this->db->query(
- "UPDATE ".$this->db->table_name($this->db_name).
- " SET changed=".$this->db->now().", ".join(', ', $write_sql).
- " WHERE contact_id=?".
- " AND user_id=?".
- " AND del<>1",
- $id,
- $this->user_id
- );
-
- $updated = $this->db->affected_rows();
- $this->result = null; // clear current result (from get_record())
- }
-
- return $updated;
- }
-
-
- private function convert_db_data($sql_arr)
- {
- $record = array();
- $record['ID'] = $sql_arr[$this->primary_key];
-
- if ($sql_arr['vcard']) {
- unset($sql_arr['email']);
- $vcard = new rcube_vcard($sql_arr['vcard'], RCMAIL_CHARSET, false, $this->vcard_fieldmap);
- $record += $vcard->get_assoc() + $sql_arr;
- }
- else {
- $record += $sql_arr;
- $record['email'] = explode(self::SEPARATOR, $record['email']);
- $record['email'] = array_map('trim', $record['email']);
- }
-
- return $record;
- }
-
-
- private function convert_save_data($save_data, $record = array())
- {
- $out = array();
- $words = '';
-
- // copy values into vcard object
- $vcard = new rcube_vcard($record['vcard'] ? $record['vcard'] : $save_data['vcard'], RCMAIL_CHARSET, false, $this->vcard_fieldmap);
- $vcard->reset();
- foreach ($save_data as $key => $values) {
- list($field, $section) = explode(':', $key);
- $fulltext = in_array($field, $this->fulltext_cols);
- foreach ((array)$values as $value) {
- if (isset($value))
- $vcard->set($field, $value, $section);
- if ($fulltext && is_array($value))
- $words .= ' ' . rcube_utils::normalize_string(join(" ", $value));
- else if ($fulltext && strlen($value) >= 3)
- $words .= ' ' . rcube_utils::normalize_string($value);
- }
- }
- $out['vcard'] = $vcard->export(false);
-
- foreach ($this->table_cols as $col) {
- $key = $col;
- if (!isset($save_data[$key]))
- $key .= ':home';
- if (isset($save_data[$key])) {
- if (is_array($save_data[$key]))
- $out[$col] = join(self::SEPARATOR, $save_data[$key]);
- else
- $out[$col] = $save_data[$key];
- }
- }
-
- // save all e-mails in database column
- $out['email'] = join(self::SEPARATOR, $vcard->email);
-
- // join words for fulltext search
- $out['words'] = join(" ", array_unique(explode(" ", $words)));
-
- return $out;
- }
-
-
- /**
- * Mark one or more contact records as deleted
- *
- * @param array Record identifiers
- * @param boolean Remove record(s) irreversible (unsupported)
- */
- function delete($ids, $force=true)
- {
- if (!is_array($ids))
- $ids = explode(self::SEPARATOR, $ids);
-
- $ids = $this->db->array2list($ids, 'integer');
-
- // flag record as deleted (always)
- $this->db->query(
- "UPDATE ".$this->db->table_name($this->db_name).
- " SET del=1, changed=".$this->db->now().
- " WHERE user_id=?".
- " AND contact_id IN ($ids)",
- $this->user_id
- );
-
- $this->cache = null;
-
- return $this->db->affected_rows();
- }
-
-
- /**
- * Undelete one or more contact records
- *
- * @param array Record identifiers
- */
- function undelete($ids)
- {
- if (!is_array($ids))
- $ids = explode(self::SEPARATOR, $ids);
-
- $ids = $this->db->array2list($ids, 'integer');
-
- // clear deleted flag
- $this->db->query(
- "UPDATE ".$this->db->table_name($this->db_name).
- " SET del=0, changed=".$this->db->now().
- " WHERE user_id=?".
- " AND contact_id IN ($ids)",
- $this->user_id
- );
-
- $this->cache = null;
-
- return $this->db->affected_rows();
- }
-
-
- /**
- * Remove all records from the database
- */
- function delete_all()
- {
- $this->cache = null;
-
- $this->db->query("UPDATE ".$this->db->table_name($this->db_name).
- " SET del=1, changed=".$this->db->now().
- " WHERE user_id = ?", $this->user_id);
-
- return $this->db->affected_rows();
- }
-
-
- /**
- * Create a contact group with the given name
- *
- * @param string The group name
- * @return mixed False on error, array with record props in success
- */
- function create_group($name)
- {
- $result = false;
-
- // make sure we have a unique name
- $name = $this->unique_groupname($name);
-
- $this->db->query(
- "INSERT INTO ".$this->db->table_name($this->db_groups).
- " (user_id, changed, name)".
- " VALUES (".intval($this->user_id).", ".$this->db->now().", ".$this->db->quote($name).")"
- );
-
- if ($insert_id = $this->db->insert_id($this->db_groups))
- $result = array('id' => $insert_id, 'name' => $name);
-
- return $result;
- }
-
-
- /**
- * Delete the given group (and all linked group members)
- *
- * @param string Group identifier
- * @return boolean True on success, false if no data was changed
- */
- function delete_group($gid)
- {
- // flag group record as deleted
- $sql_result = $this->db->query(
- "UPDATE ".$this->db->table_name($this->db_groups).
- " SET del=1, changed=".$this->db->now().
- " WHERE contactgroup_id=?".
- " AND user_id=?",
- $gid, $this->user_id
- );
-
- $this->cache = null;
-
- return $this->db->affected_rows();
- }
-
-
- /**
- * Rename a specific contact group
- *
- * @param string Group identifier
- * @param string New name to set for this group
- * @return boolean New name on success, false if no data was changed
- */
- function rename_group($gid, $newname, &$new_gid)
- {
- // make sure we have a unique name
- $name = $this->unique_groupname($newname);
-
- $sql_result = $this->db->query(
- "UPDATE ".$this->db->table_name($this->db_groups).
- " SET name=?, changed=".$this->db->now().
- " WHERE contactgroup_id=?".
- " AND user_id=?",
- $name, $gid, $this->user_id
- );
-
- return $this->db->affected_rows() ? $name : false;
- }
-
-
- /**
- * Add the given contact records the a certain group
- *
- * @param string Group identifier
- * @param array List of contact identifiers to be added
- * @return int Number of contacts added
- */
- function add_to_group($group_id, $ids)
- {
- if (!is_array($ids))
- $ids = explode(self::SEPARATOR, $ids);
-
- $added = 0;
- $exists = array();
-
- // get existing assignments ...
- $sql_result = $this->db->query(
- "SELECT contact_id FROM ".$this->db->table_name($this->db_groupmembers).
- " WHERE contactgroup_id=?".
- " AND contact_id IN (".$this->db->array2list($ids, 'integer').")",
- $group_id
- );
- while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
- $exists[] = $sql_arr['contact_id'];
- }
- // ... and remove them from the list
- $ids = array_diff($ids, $exists);
-
- foreach ($ids as $contact_id) {
- $this->db->query(
- "INSERT INTO ".$this->db->table_name($this->db_groupmembers).
- " (contactgroup_id, contact_id, created)".
- " VALUES (?, ?, ".$this->db->now().")",
- $group_id,
- $contact_id
- );
-
- if ($error = $this->db->is_error())
- $this->set_error(self::ERROR_SAVING, $error);
- else
- $added++;
- }
-
- return $added;
- }
-
-
- /**
- * Remove the given contact records from a certain group
- *
- * @param string Group identifier
- * @param array List of contact identifiers to be removed
- * @return int Number of deleted group members
- */
- function remove_from_group($group_id, $ids)
- {
- if (!is_array($ids))
- $ids = explode(self::SEPARATOR, $ids);
-
- $ids = $this->db->array2list($ids, 'integer');
-
- $sql_result = $this->db->query(
- "DELETE FROM ".$this->db->table_name($this->db_groupmembers).
- " WHERE contactgroup_id=?".
- " AND contact_id IN ($ids)",
- $group_id
- );
-
- return $this->db->affected_rows();
- }
-
-
- /**
- * Check for existing groups with the same name
- *
- * @param string Name to check
- * @return string A group name which is unique for the current use
- */
- private function unique_groupname($name)
- {
- $checkname = $name;
- $num = 2; $hit = false;
-
- do {
- $sql_result = $this->db->query(
- "SELECT 1 FROM ".$this->db->table_name($this->db_groups).
- " WHERE del<>1".
- " AND user_id=?".
- " AND name=?",
- $this->user_id,
- $checkname);
-
- // append number to make name unique
- if ($hit = $this->db->fetch_array($sql_result)) {
- $checkname = $name . ' ' . $num++;
- }
- } while ($hit);
-
- return $checkname;
- }
-
-}
diff --git a/program/include/rcube_content_filter.php b/program/include/rcube_content_filter.php
deleted file mode 100644
index 36e61a271..000000000
--- a/program/include/rcube_content_filter.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_content_filter.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
- */
-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;
- }
-}
diff --git a/program/include/rcube_db.php b/program/include/rcube_db.php
deleted file mode 100644
index b066101b3..000000000
--- a/program/include/rcube_db.php
+++ /dev/null
@@ -1,1010 +0,0 @@
-<?php
-
-/**
- +-----------------------------------------------------------------------+
- | program/include/rcube_db.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | |
- | Licensed under the GNU General Public License version 3 or |
- | any later version with exceptions for skins & plugins. |
- | See the README file for a full license statement. |
- | |
- | PURPOSE: |
- | Database wrapper class that implements PHP PDO functions |
- | |
- +-----------------------------------------------------------------------+
- | Author: Aleksander Machniak <alec@alec.pl> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Database independent query interface
- *
- * This is a wrapper for the PHP PDO
- *
- * @package Database
- * @version 1.0
- */
-class rcube_db
-{
- public $db_provider;
-
- protected $db_dsnw; // DSN for write operations
- protected $db_dsnr; // DSN for read operations
- protected $db_connected = false; // Already connected ?
- protected $db_mode; // Connection mode
- protected $dbh; // Connection handle
-
- protected $db_error = false;
- protected $db_error_msg = '';
- protected $conn_failure = false;
- protected $a_query_results = array('dummy');
- protected $last_res_id = 0;
- protected $db_index = 0;
- protected $tables;
- protected $variables;
-
- protected $options = array(
- // column/table quotes
- 'identifier_start' => '"',
- 'identifier_end' => '"',
- );
-
-
- /**
- * Factory, returns driver-specific instance of the class
- *
- * @param string $db_dsnw DSN for read/write operations
- * @param string $db_dsnr Optional DSN for read only operations
- * @param bool $pconn Enables persistent connections
- *
- * @return rcube_db Object instance
- */
- public static function factory($db_dsnw, $db_dsnr = '', $pconn = false)
- {
- $driver = strtolower(substr($db_dsnw, 0, strpos($db_dsnw, ':')));
- $driver_map = array(
- 'sqlite2' => 'sqlite',
- 'sybase' => 'mssql',
- 'dblib' => 'mssql',
- 'mysqli' => 'mysql',
- );
-
- $driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver;
- $class = "rcube_db_$driver";
-
- if (!class_exists($class)) {
- rcube::raise_error(array('code' => 600, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => "Configuration error. Unsupported database driver: $driver"),
- true, true);
- }
-
- return new $class($db_dsnw, $db_dsnr, $pconn);
- }
-
- /**
- * Object constructor
- *
- * @param string $db_dsnw DSN for read/write operations
- * @param string $db_dsnr Optional DSN for read only operations
- * @param bool $pconn Enables persistent connections
- */
- public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
- {
- if (empty($db_dsnr)) {
- $db_dsnr = $db_dsnw;
- }
-
- $this->db_dsnw = $db_dsnw;
- $this->db_dsnr = $db_dsnr;
- $this->db_pconn = $pconn;
-
- $this->db_dsnw_array = self::parse_dsn($db_dsnw);
- $this->db_dsnr_array = self::parse_dsn($db_dsnr);
-
- // Initialize driver class
- $this->init();
- }
-
- /**
- * Initialization of the object with driver specific code
- */
- protected function init()
- {
- // To be used by driver classes
- }
-
- /**
- * Connect to specific database
- *
- * @param array $dsn DSN for DB connections
- *
- * @return PDO database handle
- */
- protected function dsn_connect($dsn)
- {
- $this->db_error = false;
- $this->db_error_msg = null;
-
- // Get database specific connection options
- $dsn_string = $this->dsn_string($dsn);
- $dsn_options = $this->dsn_options($dsn);
-
- if ($db_pconn) {
- $dsn_options[PDO::ATTR_PERSISTENT] = true;
- }
-
- // Connect
- try {
- // with this check we skip fatal error on PDO object creation
- if (!class_exists('PDO', false)) {
- throw new Exception('PDO extension not loaded. See http://php.net/manual/en/intro.pdo.php');
- }
-
- $this->conn_prepare($dsn);
-
- $dbh = new PDO($dsn_string, $dsn['username'], $dsn['password'], $dsn_options);
-
- // don't throw exceptions or warnings
- $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
- }
- catch (Exception $e) {
- $this->db_error = true;
- $this->db_error_msg = $e->getMessage();
-
- rcube::raise_error(array('code' => 500, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => $this->db_error_msg), true, false);
-
- return null;
- }
-
- $this->conn_configure($dsn, $dbh);
-
- return $dbh;
- }
-
- /**
- * Driver-specific preparation of database connection
- *
- * @param array $dsn DSN for DB connections
- */
- protected function conn_prepare($dsn)
- {
- }
-
- /**
- * Driver-specific configuration of database connection
- *
- * @param array $dsn DSN for DB connections
- * @param PDO $dbh Connection handler
- */
- protected function conn_configure($dsn, $dbh)
- {
- }
-
- /**
- * Driver-specific database character set setting
- *
- * @param string $charset Character set name
- */
- protected function set_charset($charset)
- {
- $this->query("SET NAMES 'utf8'");
- }
-
- /**
- * Connect to appropriate database depending on the operation
- *
- * @param string $mode Connection mode (r|w)
- */
- public function db_connect($mode)
- {
- // previous connection failed, don't attempt to connect again
- if ($this->conn_failure) {
- return;
- }
-
- // no replication
- if ($this->db_dsnw == $this->db_dsnr) {
- $mode = 'w';
- }
-
- // Already connected
- if ($this->db_connected) {
- // connected to db with the same or "higher" mode
- if ($this->db_mode == 'w' || $this->db_mode == $mode) {
- return;
- }
- }
-
- $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array;
-
- $this->dbh = $this->dsn_connect($dsn);
- $this->db_connected = is_object($this->dbh);
-
- // use write-master when read-only fails
- if (!$this->db_connected && $mode == 'r') {
- $mode = 'w';
- $this->dbh = $this->dsn_connect($this->db_dsnw_array);
- $this->db_connected = is_object($this->dbh);
- }
-
- if ($this->db_connected) {
- $this->db_mode = $mode;
- $this->set_charset('utf8');
- }
- else {
- $this->conn_failure = true;
- }
- }
-
- /**
- * Activate/deactivate debug mode
- *
- * @param boolean $dbg True if SQL queries should be logged
- */
- public function set_debug($dbg = true)
- {
- $this->options['debug_mode'] = $dbg;
- }
-
- /**
- * Writes debug information/query to 'sql' log file
- *
- * @param string $query SQL query
- */
- protected function debug($query)
- {
- if ($this->options['debug_mode']) {
- rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';');
- }
- }
-
- /**
- * Getter for error state
- *
- * @param int $res_id Optional query result identifier
- *
- * @return string Error message
- */
- public function is_error($res_id = null)
- {
- if ($res_id !== null) {
- return $this->_get_result($res_id) === false ? $this->db_error_msg : null;
- }
-
- return $this->db_error ? $this->db_error_msg : null;
- }
-
- /**
- * Connection state checker
- *
- * @return boolean True if in connected state
- */
- public function is_connected()
- {
- return !is_object($this->dbh) ? false : $this->db_connected;
- }
-
- /**
- * Is database replication configured?
- *
- * @return bool Returns true if dsnw != dsnr
- */
- public function is_replicated()
- {
- return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr;
- }
-
- /**
- * Get database runtime variables
- *
- * @param string $varname Variable name
- * @param mixed $default Default value if variable is not set
- *
- * @return mixed Variable value or default
- */
- public function get_variable($varname, $default = null)
- {
- // to be implemented by driver class
- return $default;
- }
-
- /**
- * Execute a SQL query
- *
- * @param string SQL query to execute
- * @param mixed Values to be inserted in query
- *
- * @return number Query handle identifier
- */
- public function query()
- {
- $params = func_get_args();
- $query = array_shift($params);
-
- // Support one argument of type array, instead of n arguments
- if (count($params) == 1 && is_array($params[0])) {
- $params = $params[0];
- }
-
- return $this->_query($query, 0, 0, $params);
- }
-
- /**
- * Execute a SQL query with limits
- *
- * @param string SQL query to execute
- * @param int Offset for LIMIT statement
- * @param int Number of rows for LIMIT statement
- * @param mixed Values to be inserted in query
- *
- * @return int Query handle identifier
- */
- public function limitquery()
- {
- $params = func_get_args();
- $query = array_shift($params);
- $offset = array_shift($params);
- $numrows = array_shift($params);
-
- return $this->_query($query, $offset, $numrows, $params);
- }
-
- /**
- * Execute a SQL query with limits
- *
- * @param string $query SQL query to execute
- * @param int $offset Offset for LIMIT statement
- * @param int $numrows Number of rows for LIMIT statement
- * @param array $params Values to be inserted in query
- *
- * @return int Query handle identifier
- */
- protected function _query($query, $offset, $numrows, $params)
- {
- // Read or write ?
- $mode = preg_match('/^(select|show)/i', ltrim($query)) ? 'r' : 'w';
-
- $this->db_connect($mode);
-
- // check connection before proceeding
- if (!$this->is_connected()) {
- return null;
- }
-
- if ($numrows || $offset) {
- $query = $this->set_limit($query, $numrows, $offset);
- }
-
- $params = (array) $params;
-
- // Because in Roundcube we mostly use queries that are
- // executed only once, we will not use prepared queries
- $pos = 0;
- $idx = 0;
-
- while ($pos = strpos($query, '?', $pos)) {
- if ($query[$pos+1] == '?') { // skip escaped ?
- $pos += 2;
- }
- else {
- $val = $this->quote($params[$idx++]);
- unset($params[$idx-1]);
- $query = substr_replace($query, $val, $pos, 1);
- $pos += strlen($val);
- }
- }
-
- // replace escaped ? back to normal
- $query = rtrim(strtr($query, array('??' => '?')), ';');
-
- $this->debug($query);
-
- $query = $this->dbh->query($query);
-
- if ($query === false) {
- $error = $this->dbh->errorInfo();
- $this->db_error = true;
- $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
-
- rcube::raise_error(array('code' => 500, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => $this->db_error_msg), true, false);
- }
-
- // add result, even if it's an error
- return $this->_add_result($query);
- }
-
- /**
- * Get number of affected rows for the last query
- *
- * @param number $res_id Optional query handle identifier
- *
- * @return int Number of rows or false on failure
- */
- public function affected_rows($res_id = null)
- {
- if ($result = $this->_get_result($res_id)) {
- return $result->rowCount();
- }
-
- return 0;
- }
-
- /**
- * Get last inserted record ID
- *
- * @param string $table Table name (to find the incremented sequence)
- *
- * @return mixed ID or false on failure
- */
- public function insert_id($table = '')
- {
- if (!$this->db_connected || $this->db_mode == 'r') {
- return false;
- }
-
- if ($table) {
- // resolve table name
- $table = $this->table_name($table);
- }
-
- $id = $this->dbh->lastInsertId($table);
-
- return $id;
- }
-
- /**
- * Get an associative array for one row
- * If no query handle is specified, the last query will be taken as reference
- *
- * @param int $res_id Optional query handle identifier
- *
- * @return mixed Array with col values or false on failure
- */
- public function fetch_assoc($res_id = null)
- {
- $result = $this->_get_result($res_id);
- return $this->_fetch_row($result, PDO::FETCH_ASSOC);
- }
-
- /**
- * Get an index array for one row
- * If no query handle is specified, the last query will be taken as reference
- *
- * @param int $res_id Optional query handle identifier
- *
- * @return mixed Array with col values or false on failure
- */
- public function fetch_array($res_id = null)
- {
- $result = $this->_get_result($res_id);
- return $this->_fetch_row($result, PDO::FETCH_NUM);
- }
-
- /**
- * Get col values for a result row
- *
- * @param PDOStatement $result Result handle
- * @param int $mode Fetch mode identifier
- *
- * @return mixed Array with col values or false on failure
- */
- protected function _fetch_row($result, $mode)
- {
- if (!is_object($result) || !$this->is_connected()) {
- return false;
- }
-
- return $result->fetch($mode);
- }
-
- /**
- * Adds LIMIT,OFFSET clauses to the query
- *
- * @param string $query SQL query
- * @param int $limit Number of rows
- * @param int $offset Offset
- *
- * @return string SQL query
- */
- protected function set_limit($query, $limit = 0, $offset = 0)
- {
- if ($limit) {
- $query .= ' LIMIT ' . intval($limit);
- }
-
- if ($offset) {
- $query .= ' OFFSET ' . intval($offset);
- }
-
- return $query;
- }
-
- /**
- * Returns list of tables in a database
- *
- * @return array List of all tables of the current database
- */
- public function list_tables()
- {
- // get tables if not cached
- if ($this->tables === null) {
- $q = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME');
-
- if ($res = $this->_get_result($q)) {
- $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0);
- }
- else {
- $this->tables = array();
- }
- }
-
- return $this->tables;
- }
-
- /**
- * Returns list of columns in database table
- *
- * @param string $table Table name
- *
- * @return array List of table cols
- */
- public function list_cols($table)
- {
- $q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?',
- array($table));
-
- if ($res = $this->_get_result($q)) {
- return $res->fetchAll(PDO::FETCH_COLUMN, 0);
- }
-
- return array();
- }
-
- /**
- * Formats input so it can be safely used in a query
- *
- * @param mixed $input Value to quote
- * @param string $type Type of data
- *
- * @return string Quoted/converted string for use in query
- */
- public function quote($input, $type = null)
- {
- // handle int directly for better performance
- if ($type == 'integer' || $type == 'int') {
- return intval($input);
- }
-
- if (is_null($input)) {
- return 'NULL';
- }
-
- // create DB handle if not available
- if (!$this->dbh) {
- $this->db_connect('r');
- }
-
- if ($this->dbh) {
- $map = array(
- 'bool' => PDO::PARAM_BOOL,
- 'integer' => PDO::PARAM_INT,
- );
- $type = isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
- return strtr($this->dbh->quote($input, $type), array('?' => '??')); // escape ?
- }
-
- return 'NULL';
- }
-
- /**
- * Quotes a string so it can be safely used as a table or column name
- *
- * @param string $str Value to quote
- *
- * @return string Quoted string for use in query
- * @deprecated Replaced by rcube_db::quote_identifier
- * @see rcube_db::quote_identifier
- */
- public function quoteIdentifier($str)
- {
- return $this->quote_identifier($str);
- }
-
- /**
- * Quotes a string so it can be safely used as a table or column name
- *
- * @param string $str Value to quote
- *
- * @return string Quoted string for use in query
- */
- public function quote_identifier($str)
- {
- $start = $this->options['identifier_start'];
- $end = $this->options['identifier_end'];
- $name = array();
-
- foreach (explode('.', $str) as $elem) {
- $elem = str_replace(array($start, $end), '', $elem);
- $name[] = $start . $elem . $end;
- }
-
- return implode($name, '.');
- }
-
- /**
- * Return SQL function for current time and date
- *
- * @return string SQL function to use in query
- */
- public function now()
- {
- return "now()";
- }
-
- /**
- * Return list of elements for use with SQL's IN clause
- *
- * @param array $arr Input array
- * @param string $type Type of data
- *
- * @return string Comma-separated list of quoted values for use in query
- */
- public function array2list($arr, $type = null)
- {
- if (!is_array($arr)) {
- return $this->quote($arr, $type);
- }
-
- foreach ($arr as $idx => $item) {
- $arr[$idx] = $this->quote($item, $type);
- }
-
- return implode(',', $arr);
- }
-
- /**
- * Return SQL statement to convert a field value into a unix timestamp
- *
- * This method is deprecated and should not be used anymore due to limitations
- * of timestamp functions in Mysql (year 2038 problem)
- *
- * @param string $field Field name
- *
- * @return string SQL statement to use in query
- * @deprecated
- */
- public function unixtimestamp($field)
- {
- return "UNIX_TIMESTAMP($field)";
- }
-
- /**
- * Return SQL statement to convert from a unix timestamp
- *
- * @param int $timestamp Unix timestamp
- *
- * @return string Date string in db-specific format
- */
- public function fromunixtime($timestamp)
- {
- return date("'Y-m-d H:i:s'", $timestamp);
- }
-
- /**
- * Return SQL statement for case insensitive LIKE
- *
- * @param string $column Field name
- * @param string $value Search value
- *
- * @return string SQL statement to use in query
- */
- public function ilike($column, $value)
- {
- return $this->quote_identifier($column).' LIKE '.$this->quote($value);
- }
-
- /**
- * Abstract SQL statement for value concatenation
- *
- * @return string SQL statement to be used in query
- */
- public function concat(/* col1, col2, ... */)
- {
- $args = func_get_args();
- if (is_array($args[0])) {
- $args = $args[0];
- }
-
- return '(' . join(' || ', $args) . ')';
- }
-
- /**
- * Encodes non-UTF-8 characters in string/array/object (recursive)
- *
- * @param mixed $input Data to fix
- *
- * @return mixed Properly UTF-8 encoded data
- */
- public static function encode($input)
- {
- if (is_object($input)) {
- foreach (get_object_vars($input) as $idx => $value) {
- $input->$idx = self::encode($value);
- }
- return $input;
- }
- else if (is_array($input)) {
- foreach ($input as $idx => $value) {
- $input[$idx] = self::encode($value);
- }
- return $input;
- }
-
- return utf8_encode($input);
- }
-
- /**
- * Decodes encoded UTF-8 string/object/array (recursive)
- *
- * @param mixed $input Input data
- *
- * @return mixed Decoded data
- */
- public static function decode($input)
- {
- if (is_object($input)) {
- foreach (get_object_vars($input) as $idx => $value) {
- $input->$idx = self::decode($value);
- }
- return $input;
- }
- else if (is_array($input)) {
- foreach ($input as $idx => $value) {
- $input[$idx] = self::decode($value);
- }
- return $input;
- }
-
- return utf8_decode($input);
- }
-
- /**
- * Adds a query result and returns a handle ID
- *
- * @param object $res Query handle
- *
- * @return int Handle ID
- */
- protected function _add_result($res)
- {
- $this->last_res_id = sizeof($this->a_query_results);
- $this->a_query_results[$this->last_res_id] = $res;
-
- return $this->last_res_id;
- }
-
- /**
- * Resolves a given handle ID and returns the according query handle
- * If no ID is specified, the last resource handle will be returned
- *
- * @param int $res_id Handle ID
- *
- * @return mixed Resource handle or false on failure
- */
- protected function _get_result($res_id = null)
- {
- if ($res_id == null) {
- $res_id = $this->last_res_id;
- }
-
- if (!empty($this->a_query_results[$res_id])) {
- return $this->a_query_results[$res_id];
- }
-
- return false;
- }
-
- /**
- * Return correct name for a specific database table
- *
- * @param string $table Table name
- *
- * @return string Translated table name
- */
- public function table_name($table)
- {
- $rcube = rcube::get_instance();
-
- // return table name if configured
- $config_key = 'db_table_'.$table;
-
- if ($name = $rcube->config->get($config_key)) {
- return $name;
- }
-
- return $table;
- }
-
- /**
- * MDB2 DSN string parser
- *
- * @param string $sequence Secuence name
- *
- * @return array DSN parameters
- */
- public static function parse_dsn($dsn)
- {
- if (empty($dsn)) {
- return null;
- }
-
- // Find phptype and dbsyntax
- if (($pos = strpos($dsn, '://')) !== false) {
- $str = substr($dsn, 0, $pos);
- $dsn = substr($dsn, $pos + 3);
- }
- else {
- $str = $dsn;
- $dsn = null;
- }
-
- // Get phptype and dbsyntax
- // $str => phptype(dbsyntax)
- if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
- $parsed['phptype'] = $arr[1];
- $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
- }
- else {
- $parsed['phptype'] = $str;
- $parsed['dbsyntax'] = $str;
- }
-
- if (empty($dsn)) {
- return $parsed;
- }
-
- // Get (if found): username and password
- // $dsn => username:password@protocol+hostspec/database
- if (($at = strrpos($dsn,'@')) !== false) {
- $str = substr($dsn, 0, $at);
- $dsn = substr($dsn, $at + 1);
- if (($pos = strpos($str, ':')) !== false) {
- $parsed['username'] = rawurldecode(substr($str, 0, $pos));
- $parsed['password'] = rawurldecode(substr($str, $pos + 1));
- }
- else {
- $parsed['username'] = rawurldecode($str);
- }
- }
-
- // Find protocol and hostspec
-
- // $dsn => proto(proto_opts)/database
- if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
- $proto = $match[1];
- $proto_opts = $match[2] ? $match[2] : false;
- $dsn = $match[3];
- }
- // $dsn => protocol+hostspec/database (old format)
- else {
- if (strpos($dsn, '+') !== false) {
- list($proto, $dsn) = explode('+', $dsn, 2);
- }
- if ( strpos($dsn, '//') === 0
- && strpos($dsn, '/', 2) !== false
- && $parsed['phptype'] == 'oci8'
- ) {
- //oracle's "Easy Connect" syntax:
- //"username/password@[//]host[:port][/service_name]"
- //e.g. "scott/tiger@//mymachine:1521/oracle"
- $proto_opts = $dsn;
- $pos = strrpos($proto_opts, '/');
- $dsn = substr($proto_opts, $pos + 1);
- $proto_opts = substr($proto_opts, 0, $pos);
- }
- else if (strpos($dsn, '/') !== false) {
- list($proto_opts, $dsn) = explode('/', $dsn, 2);
- }
- else {
- $proto_opts = $dsn;
- $dsn = null;
- }
- }
-
- // process the different protocol options
- $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
- $proto_opts = rawurldecode($proto_opts);
- if (strpos($proto_opts, ':') !== false) {
- list($proto_opts, $parsed['port']) = explode(':', $proto_opts);
- }
- if ($parsed['protocol'] == 'tcp') {
- $parsed['hostspec'] = $proto_opts;
- }
- else if ($parsed['protocol'] == 'unix') {
- $parsed['socket'] = $proto_opts;
- }
-
- // Get dabase if any
- // $dsn => database
- if ($dsn) {
- // /database
- if (($pos = strpos($dsn, '?')) === false) {
- $parsed['database'] = rawurldecode($dsn);
- // /database?param1=value1&param2=value2
- }
- else {
- $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
- $dsn = substr($dsn, $pos + 1);
- if (strpos($dsn, '&') !== false) {
- $opts = explode('&', $dsn);
- }
- else { // database?param1=value1
- $opts = array($dsn);
- }
- foreach ($opts as $opt) {
- list($key, $value) = explode('=', $opt);
- if (!array_key_exists($key, $parsed) || false === $parsed[$key]) {
- // don't allow params overwrite
- $parsed[$key] = rawurldecode($value);
- }
- }
- }
- }
-
- return $parsed;
- }
-
- /**
- * Returns PDO DSN string from DSN array
- *
- * @param array $dsn DSN parameters
- *
- * @return string DSN string
- */
- protected function dsn_string($dsn)
- {
- $params = array();
- $result = $dsn['phptype'] . ':';
-
- if ($dsn['hostspec']) {
- $params[] = 'host=' . $dsn['hostspec'];
- }
-
- if ($dsn['port']) {
- $params[] = 'port=' . $dsn['port'];
- }
-
- if ($dsn['database']) {
- $params[] = 'dbname=' . $dsn['database'];
- }
-
- 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 = array();
-
- return $result;
- }
-}
diff --git a/program/include/rcube_db_mssql.php b/program/include/rcube_db_mssql.php
deleted file mode 100644
index 119647d95..000000000
--- a/program/include/rcube_db_mssql.php
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-
-/**
- +-----------------------------------------------------------------------+
- | program/include/rcube_db_mssql.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 Database
- * @version 1.0
- */
-class rcube_db_mssql extends rcube_db
-{
- public $db_provider = 'mssql';
-
- /**
- * Driver initialization
- */
- protected function init()
- {
- $this->options['identifier_start'] = '[';
- $this->options['identifier_end'] = ']';
- }
-
- /**
- * Character setting
- */
- protected function set_charset($charset)
- {
- // UTF-8 is default
- }
-
- /**
- * Return SQL function for current time and date
- *
- * @return string SQL function to use in query
- */
- public function now()
- {
- return "getdate()";
- }
-
- /**
- * Return SQL statement to convert a field value into a unix timestamp
- *
- * This method is deprecated and should not be used anymore due to limitations
- * of timestamp functions in Mysql (year 2038 problem)
- *
- * @param string $field Field name
- *
- * @return string SQL statement to use in query
- * @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);
-
- $orderby = stristr($query, 'ORDER BY');
- if ($orderby !== false) {
- $sort = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc';
- $order = str_ireplace('ORDER BY', '', $orderby);
- $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order));
- }
-
- $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query);
-
- $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl';
- if ($orderby !== false) {
- $query .= ' ORDER BY ' . $order . ' ';
- $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC';
- }
- $query .= ') AS outer_tbl';
- if ($orderby !== false) {
- $query .= ' ORDER BY ' . $order . ' ' . $sort;
- }
-
- 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;
- }
-}
diff --git a/program/include/rcube_db_mysql.php b/program/include/rcube_db_mysql.php
deleted file mode 100644
index 2cdcf3021..000000000
--- a/program/include/rcube_db_mysql.php
+++ /dev/null
@@ -1,156 +0,0 @@
-<?php
-
-/**
- +-----------------------------------------------------------------------+
- | program/include/rcube_db_mysql.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 Database
- * @version 1.0
- */
-class rcube_db_mysql extends rcube_db
-{
- public $db_provider = 'mysql';
-
- /**
- * Driver initialization/configuration
- */
- protected function init()
- {
- // SQL identifiers quoting
- $this->options['identifier_start'] = '`';
- $this->options['identifier_end'] = '`';
- }
-
- /**
- * 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 = array();
-
- if (!empty($dsn['key'])) {
- $result[PDO::MYSQL_ATTR_KEY] = $dsn['key'];
- }
-
- if (!empty($dsn['cipher'])) {
- $result[PDO::MYSQL_ATTR_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'];
- }
-
- return $result;
- }
-
- /**
- * 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();
-
- $result = $this->query('SHOW VARIABLES');
-
- while ($sql_arr = $this->fetch_array($result)) {
- $this->variables[$row[0]] = $row[1];
- }
- }
-
- return isset($this->variables[$varname]) ? $this->variables[$varname] : $default;
- }
-
-}
diff --git a/program/include/rcube_db_pgsql.php b/program/include/rcube_db_pgsql.php
deleted file mode 100644
index 0d0caadde..000000000
--- a/program/include/rcube_db_pgsql.php
+++ /dev/null
@@ -1,137 +0,0 @@
-<?php
-
-/**
- +-----------------------------------------------------------------------+
- | program/include/rcube_db_pgsql.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 Database
- * @version 1.0
- */
-class rcube_db_pgsql extends rcube_db
-{
- public $db_provider = 'postgres';
-
- /**
- * 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 $sequence Secuence name
- *
- * @return string Translated sequence name
- */
- protected function sequence_name($sequence)
- {
- $rcube = rcube::get_instance();
-
- // return sequence name if configured
- $config_key = 'db_sequence_'.$sequence;
-
- if ($name = $rcube->config->get($config_key)) {
- return $name;
- }
-
- return $sequence;
- }
-
- /**
- * Return SQL statement to convert a field value into a unix timestamp
- *
- * This method is deprecated and should not be used anymore due to limitations
- * of timestamp functions in Mysql (year 2038 problem)
- *
- * @param string $field Field name
- *
- * @return string SQL statement to use in query
- * @deprecated
- */
- public function unixtimestamp($field)
- {
- return "EXTRACT (EPOCH FROM $field)";
- }
-
- /**
- * 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 $default;
- }
-
- 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;
- }
-
-}
diff --git a/program/include/rcube_db_sqlite.php b/program/include/rcube_db_sqlite.php
deleted file mode 100644
index a7397674b..000000000
--- a/program/include/rcube_db_sqlite.php
+++ /dev/null
@@ -1,180 +0,0 @@
-<?php
-
-/**
- +-----------------------------------------------------------------------+
- | program/include/rcube_db_sqlite.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 Database
- * @version 1.0
- */
-class rcube_db_sqlite extends rcube_db
-{
- public $db_provider = 'sqlite';
-
- /**
- * Database character set
- */
- protected function set_charset($charset)
- {
- }
-
- /**
- * Prepare connection
- */
- protected function conn_prepare($dsn)
- {
- // 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)
- {
- // we emulate via callback some missing functions
- $dbh->sqliteCreateFunction('unix_timestamp', array('rcube_db_sqlite', 'sqlite_unix_timestamp'), 1);
- $dbh->sqliteCreateFunction('now', array('rcube_db_sqlite', 'sqlite_now'), 0);
-
- // Initialize database structure in file is empty
- if (!empty($dsn['database']) && !filesize($dsn['database'])) {
- $data = file_get_contents(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);
- }
- }
- }
- }
-
- /**
- * Callback for sqlite: unix_timestamp()
- */
- public static function sqlite_unix_timestamp($timestamp = '')
- {
- $timestamp = trim($timestamp);
- if (!$timestamp) {
- $ret = time();
- }
- else if (!preg_match('/^[0-9]+$/s', $timestamp)) {
- $ret = strtotime($timestamp);
- }
- else {
- $ret = $timestamp;
- }
-
- return $ret;
- }
-
- /**
- * Callback for sqlite: now()
- */
- public static function sqlite_now()
- {
- return date("Y-m-d H:i:s");
- }
-
- /**
- * 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');
-
- if ($res = $this->_get_result($q)) {
- $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0);
- }
- else {
- $this->tables = array();
- }
- }
-
- return $this->tables;
- }
-
- /**
- * Returns list of columns in database table
- *
- * @param string $table Table name
- *
- * @return array List of table cols
- */
- public function list_cols($table)
- {
- $q = $this->query('SELECT 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'];
- }
-}
diff --git a/program/include/rcube_db_sqlsrv.php b/program/include/rcube_db_sqlsrv.php
deleted file mode 100644
index e58bf0704..000000000
--- a/program/include/rcube_db_sqlsrv.php
+++ /dev/null
@@ -1,158 +0,0 @@
-<?php
-
-/**
- +-----------------------------------------------------------------------+
- | program/include/rcube_db_sqlsrv.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 Database
- * @version 1.0
- */
-class rcube_db_sqlsrv extends rcube_db
-{
- public $db_provider = 'mssql';
-
- /**
- * Driver initialization
- */
- protected function init()
- {
- $this->options['identifier_start'] = '[';
- $this->options['identifier_end'] = ']';
- }
-
- /**
- * Database character set setting
- */
- protected function set_charset($charset)
- {
- // UTF-8 is default
- }
-
- /**
- * Return SQL function for current time and date
- *
- * @return string SQL function to use in query
- */
- public function now()
- {
- return "getdate()";
- }
-
- /**
- * Return SQL statement to convert a field value into a unix timestamp
- *
- * This method is deprecated and should not be used anymore due to limitations
- * of timestamp functions in Mysql (year 2038 problem)
- *
- * @param string $field Field name
- *
- * @return string SQL statement to use in query
- * @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);
-
- $orderby = stristr($query, 'ORDER BY');
- if ($orderby !== false) {
- $sort = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc';
- $order = str_ireplace('ORDER BY', '', $orderby);
- $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order));
- }
-
- $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query);
-
- $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl';
- if ($orderby !== false) {
- $query .= ' ORDER BY ' . $order . ' ';
- $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC';
- }
- $query .= ') AS outer_tbl';
- if ($orderby !== false) {
- $query .= ' ORDER BY ' . $order . ' ' . $sort;
- }
-
- return $query;
- }
-
- /**
- * 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;
- }
-}
diff --git a/program/include/rcube_image.php b/program/include/rcube_image.php
deleted file mode 100644
index 80e8bd4f3..000000000
--- a/program/include/rcube_image.php
+++ /dev/null
@@ -1,251 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_image.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | |
- | 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> |
- +-----------------------------------------------------------------------+
-*/
-
-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',
- );
-
-
- 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($imsize['2'], false);
- }
-
- // use ImageMagick
- if (!$type && ($data = $this->identify())) {
- list($type, $width, $height) = $data;
- }
-
- if ($type) {
- return array(
- 'type' => $type,
- 'gd_type' => $gd_type,
- 'width' => $width,
- 'height' => $height,
- );
- }
- }
-
- /**
- * Resize image to a given size
- *
- * @param int $size Max width/height size
- * @param string $filename Output filename
- *
- * @return bool True on success, False on failure
- */
- public function resize($size, $filename = null)
- {
- $result = false;
- $rcube = rcube::get_instance();
- $convert = $rcube->config->get('im_convert_path', false);
- $props = $this->props();
-
- if (!$filename) {
- $filename = $this->image_file;
- }
-
- // use Imagemagick
- if ($convert) {
- $p['out'] = $filename;
- $p['in'] = $this->image_file;
- $p['size'] = $size.'x'.$size;
- $type = $props['type'];
-
- if (!$type && ($data = $this->identify())) {
- $type = $data[0];
- }
-
- $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
- $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
- $p['-opts'] = array('-resize' => $size.'>');
-
- if (in_array($type, explode(',', $p['types']))) { // Valid type?
- $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p);
- }
-
- if ($result === '') {
- return true;
- }
- }
-
- // use GD extension
- $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG);
- if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) {
- if ($props['gd_type'] == IMAGETYPE_JPEG) {
- $image = imagecreatefromjpeg($this->image_file);
- }
- elseif($props['gd_type'] == IMAGETYPE_GIF) {
- $image = imagecreatefromgif($this->image_file);
- }
- elseif($props['gd_type'] == IMAGETYPE_PNG) {
- $image = imagecreatefrompng($this->image_file);
- }
-
- $scale = $size / max($props['width'], $props['height']);
- $width = $props['width'] * $scale;
- $height = $props['height'] * $scale;
-
- $new_image = imagecreatetruecolor($width, $height);
-
- // Fix transparency of gif/png image
- if ($props['gd_type'] != IMAGETYPE_JPEG) {
- imagealphablending($new_image, false);
- imagesavealpha($new_image, true);
- $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
- imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
- }
-
- imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']);
- $image = $new_image;
-
- if ($props['gd_type'] == IMAGETYPE_JPEG) {
- $result = imagejpeg($image, $filename, 75);
- }
- elseif($props['gd_type'] == IMAGETYPE_GIF) {
- $result = imagegif($image, $filename);
- }
- elseif($props['gd_type'] == IMAGETYPE_PNG) {
- $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
- }
-
- if ($result) {
- return true;
- }
- }
-
- // @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
- if ($convert) {
- $p['in'] = $this->image_file;
- $p['out'] = $filename;
- $p['type'] = self::$extensions[$type];
-
- $result = rcube::exec($convert . ' 2>&1 -colorspace RGB -quality 75 {in} {type}:{out}', $p);
-
- if ($result === '') {
- return true;
- }
- }
-
- // use GD extension (TIFF isn't supported)
- $props = $this->props();
- $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG);
-
- if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) {
- if ($props['gd_type'] == IMAGETYPE_JPEG) {
- $image = imagecreatefromjpeg($this->image_file);
- }
- else if ($props['gd_type'] == IMAGETYPE_GIF) {
- $image = imagecreatefromgif($this->image_file);
- }
- else if ($props['gd_type'] == IMAGETYPE_PNG) {
- $image = imagecreatefrompng($this->image_file);
- }
-
- 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);
- }
- }
-
- // @TODO: print error to the log?
- return false;
- }
-
- /**
- * Identify command handler.
- */
- private function identify()
- {
- $rcube = rcube::get_instance();
-
- 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));
- }
- }
- }
-
-}
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
deleted file mode 100644
index a89fd164d..000000000
--- a/program/include/rcube_imap.php
+++ /dev/null
@@ -1,4167 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_imap.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | |
- | 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: |
- | IMAP Storage Engine |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- | Author: Aleksander Machniak <alec@alec.pl> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Interface class for accessing an IMAP server
- *
- * @package Mail
- * @author Thomas Bruederli <roundcube@gmail.com>
- * @author Aleksander Machniak <alec@alec.pl>
- * @version 2.0
- */
-class rcube_imap extends rcube_storage
-{
- /**
- * Instance of rcube_imap_generic
- *
- * @var rcube_imap_generic
- */
- public $conn;
-
- /**
- * Instance of rcube_imap_cache
- *
- * @var rcube_imap_cache
- */
- protected $mcache;
-
- /**
- * Instance of rcube_cache
- *
- * @var rcube_cache
- */
- protected $cache;
-
- /**
- * Internal (in-memory) cache
- *
- * @var array
- */
- protected $icache = array();
-
- protected $list_page = 1;
- protected $delimiter;
- protected $namespace;
- protected $sort_field = '';
- protected $sort_order = 'DESC';
- protected $struct_charset;
- protected $uid_id_map = array();
- protected $msg_headers = array();
- protected $search_set;
- protected $search_string = '';
- protected $search_charset = '';
- protected $search_sort_field = '';
- protected $search_threads = false;
- protected $search_sorted = false;
- protected $options = array('auth_method' => 'check');
- protected $caching = false;
- protected $messages_caching = false;
- protected $threading = false;
-
-
- /**
- * Object constructor.
- */
- public function __construct()
- {
- $this->conn = new rcube_imap_generic();
-
- // Set namespace and delimiter from session,
- // so some methods would work before connection
- if (isset($_SESSION['imap_namespace'])) {
- $this->namespace = $_SESSION['imap_namespace'];
- }
- if (isset($_SESSION['imap_delimiter'])) {
- $this->delimiter = $_SESSION['imap_delimiter'];
- }
- }
-
-
- /**
- * Magic getter for backward compat.
- *
- * @deprecated.
- */
- public function __get($name)
- {
- if (isset($this->{$name})) {
- return $this->{$name};
- }
- }
-
-
- /**
- * Connect to an IMAP 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
- */
- public function connect($host, $user, $pass, $port=143, $use_ssl=null)
- {
- // check for OpenSSL support in PHP build
- if ($use_ssl && extension_loaded('openssl')) {
- $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
- }
- else if ($use_ssl) {
- rcube::raise_error(array('code' => 403, 'type' => 'imap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "OpenSSL not available"), true, false);
- $port = 143;
- }
-
- $this->options['port'] = $port;
-
- if ($this->options['debug']) {
- $this->set_debug(true);
-
- $this->options['ident'] = array(
- 'name' => 'Roundcube Webmail',
- 'version' => RCMAIL_VERSION,
- 'php' => PHP_VERSION,
- 'os' => PHP_OS,
- 'command' => $_SERVER['REQUEST_URI'],
- );
- }
-
- $attempt = 0;
- do {
- $data = rcube::get_instance()->plugins->exec_hook('imap_connect',
- array_merge($this->options, array('host' => $host, 'user' => $user,
- 'attempt' => ++$attempt)));
-
- if (!empty($data['pass'])) {
- $pass = $data['pass'];
- }
-
- $this->conn->connect($data['host'], $data['user'], $pass, $data);
- } while(!$this->conn->connected() && $data['retry']);
-
- $config = array(
- 'host' => $data['host'],
- 'user' => $data['user'],
- 'password' => $pass,
- 'port' => $port,
- 'ssl' => $use_ssl,
- );
-
- $this->options = array_merge($this->options, $config);
- $this->connect_done = true;
-
- if ($this->conn->connected()) {
- // get namespace and delimiter
- $this->set_env();
- return true;
- }
- // write error log
- else if ($this->conn->error) {
- if ($pass && $user) {
- $message = sprintf("Login failed for %s from %s. %s",
- $user, rcube_utils::remote_ip(), $this->conn->error);
-
- rcube::raise_error(array('code' => 403, 'type' => 'imap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => $message), true, false);
- }
- }
-
- return false;
- }
-
-
- /**
- * Close IMAP connection.
- * Usually done on script shutdown
- */
- public function close()
- {
- $this->conn->closeConnection();
- if ($this->mcache) {
- $this->mcache->close();
- }
- }
-
-
- /**
- * Check connection state, connect if not connected.
- *
- * @return bool Connection state.
- */
- public function check_connection()
- {
- // Establish connection if it wasn't done yet
- if (!$this->connect_done && !empty($this->options['user'])) {
- return $this->connect(
- $this->options['host'],
- $this->options['user'],
- $this->options['password'],
- $this->options['port'],
- $this->options['ssl']
- );
- }
-
- return $this->is_connected();
- }
-
-
- /**
- * Checks IMAP connection.
- *
- * @return boolean TRUE on success, FALSE on failure
- */
- public function is_connected()
- {
- return $this->conn->connected();
- }
-
-
- /**
- * Returns code of last error
- *
- * @return int Error code
- */
- public function get_error_code()
- {
- return $this->conn->errornum;
- }
-
-
- /**
- * Returns text of last error
- *
- * @return string Error string
- */
- public function get_error_str()
- {
- return $this->conn->error;
- }
-
-
- /**
- * Returns code of last command response
- *
- * @return int Response code
- */
- public function get_response_code()
- {
- switch ($this->conn->resultcode) {
- case 'NOPERM':
- return self::NOPERM;
- case 'READ-ONLY':
- return self::READONLY;
- case 'TRYCREATE':
- return self::TRYCREATE;
- case 'INUSE':
- return self::INUSE;
- case 'OVERQUOTA':
- return self::OVERQUOTA;
- case 'ALREADYEXISTS':
- return self::ALREADYEXISTS;
- case 'NONEXISTENT':
- return self::NONEXISTENT;
- case 'CONTACTADMIN':
- return self::CONTACTADMIN;
- default:
- return self::UNKNOWN;
- }
- }
-
-
- /**
- * Activate/deactivate debug mode
- *
- * @param boolean $dbg True if IMAP conversation should be logged
- */
- public function set_debug($dbg = true)
- {
- $this->options['debug'] = $dbg;
- $this->conn->setDebug($dbg, array($this, 'debug_handler'));
- }
-
-
- /**
- * 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;
-
- // clear messagecount cache for this folder
- $this->clear_messagecount($folder);
- }
-
-
- /**
- * Save a search result for future message listing methods
- *
- * @param array $set Search set, result from rcube_imap::get_search_set():
- * 0 - searching criteria, string
- * 1 - search result, rcube_result_index|rcube_result_thread
- * 2 - searching character set, string
- * 3 - sorting field, string
- * 4 - true if sorted, bool
- */
- public function set_search_set($set)
- {
- $set = (array)$set;
-
- $this->search_string = $set[0];
- $this->search_set = $set[1];
- $this->search_charset = $set[2];
- $this->search_sort_field = $set[3];
- $this->search_sorted = $set[4];
- $this->search_threads = is_a($this->search_set, 'rcube_result_thread');
- }
-
-
- /**
- * Return the saved search set as hash array
- *
- * @return array Search set
- */
- public function get_search_set()
- {
- if (empty($this->search_set)) {
- return null;
- }
-
- return array(
- $this->search_string,
- $this->search_set,
- $this->search_charset,
- $this->search_sort_field,
- $this->search_sorted,
- );
- }
-
-
- /**
- * Returns the IMAP server's capability.
- *
- * @param string $cap Capability name
- *
- * @return mixed Capability value or TRUE if supported, FALSE if not
- */
- public function get_capability($cap)
- {
- $cap = strtoupper($cap);
- $sess_key = "STORAGE_$cap";
-
- if (!isset($_SESSION[$sess_key])) {
- if (!$this->check_connection()) {
- return false;
- }
-
- $_SESSION[$sess_key] = $this->conn->getCapability($cap);
- }
-
- return $_SESSION[$sess_key];
- }
-
-
- /**
- * Checks the PERMANENTFLAGS capability of the current folder
- * and returns true if the given flag is supported by the IMAP server
- *
- * @param string $flag Permanentflag name
- *
- * @return boolean True if this flag is supported
- */
- public function check_permflag($flag)
- {
- $flag = strtoupper($flag);
- $imap_flag = $this->conn->flags[$flag];
- $perm_flags = $this->get_permflags($this->folder);
-
- return in_array_nocase($imap_flag, $perm_flags);
- }
-
-
- /**
- * Returns PERMANENTFLAGS of the specified folder
- *
- * @param string $folder Folder name
- *
- * @return array Flags
- */
- public function get_permflags($folder)
- {
- if (!strlen($folder)) {
- return array();
- }
-/*
- Checking PERMANENTFLAGS is rather rare, so we disable caching of it
- Re-think when we'll use it for more than only MDNSENT flag
-
- $cache_key = 'mailboxes.permanentflags.' . $folder;
- $permflags = $this->get_cache($cache_key);
-
- if ($permflags !== null) {
- return explode(' ', $permflags);
- }
-*/
- if (!$this->check_connection()) {
- return array();
- }
-
- if ($this->conn->select($folder)) {
- $permflags = $this->conn->data['PERMANENTFLAGS'];
- }
- else {
- return array();
- }
-
- if (!is_array($permflags)) {
- $permflags = array();
- }
-/*
- // Store permflags as string to limit cached object size
- $this->update_cache($cache_key, implode(' ', $permflags));
-*/
- return $permflags;
- }
-
-
- /**
- * Returns the delimiter that is used by the IMAP server for folder separation
- *
- * @return string Delimiter string
- * @access public
- */
- public function get_hierarchy_delimiter()
- {
- return $this->delimiter;
- }
-
-
- /**
- * Get namespace
- *
- * @param string $name Namespace array index: personal, other, shared, prefix
- *
- * @return array Namespace data
- */
- public function get_namespace($name = null)
- {
- $ns = $this->namespace;
-
- if ($name) {
- return isset($ns[$name]) ? $ns[$name] : null;
- }
-
- unset($ns['prefix']);
- return $ns;
- }
-
-
- /**
- * Sets delimiter and namespaces
- */
- protected function set_env()
- {
- if ($this->delimiter !== null && $this->namespace !== null) {
- return;
- }
-
- $config = rcube::get_instance()->config;
- $imap_personal = $config->get('imap_ns_personal');
- $imap_other = $config->get('imap_ns_other');
- $imap_shared = $config->get('imap_ns_shared');
- $imap_delimiter = $config->get('imap_delimiter');
-
- if (!$this->check_connection()) {
- return;
- }
-
- $ns = $this->conn->getNamespace();
-
- // Set namespaces (NAMESPACE supported)
- if (is_array($ns)) {
- $this->namespace = $ns;
- }
- else {
- $this->namespace = array(
- 'personal' => NULL,
- 'other' => NULL,
- 'shared' => NULL,
- );
- }
-
- if ($imap_delimiter) {
- $this->delimiter = $imap_delimiter;
- }
- if (empty($this->delimiter)) {
- $this->delimiter = $this->namespace['personal'][0][1];
- }
- if (empty($this->delimiter)) {
- $this->delimiter = $this->conn->getHierarchyDelimiter();
- }
- if (empty($this->delimiter)) {
- $this->delimiter = '/';
- }
-
- // Overwrite namespaces
- if ($imap_personal !== null) {
- $this->namespace['personal'] = NULL;
- foreach ((array)$imap_personal as $dir) {
- $this->namespace['personal'][] = array($dir, $this->delimiter);
- }
- }
- if ($imap_other !== null) {
- $this->namespace['other'] = NULL;
- foreach ((array)$imap_other as $dir) {
- if ($dir) {
- $this->namespace['other'][] = array($dir, $this->delimiter);
- }
- }
- }
- if ($imap_shared !== null) {
- $this->namespace['shared'] = NULL;
- foreach ((array)$imap_shared as $dir) {
- if ($dir) {
- $this->namespace['shared'][] = array($dir, $this->delimiter);
- }
- }
- }
-
- // Find personal namespace prefix for mod_folder()
- // Prefix can be removed when there is only one personal namespace
- if (is_array($this->namespace['personal']) && count($this->namespace['personal']) == 1) {
- $this->namespace['prefix'] = $this->namespace['personal'][0][0];
- }
-
- $_SESSION['imap_namespace'] = $this->namespace;
- $_SESSION['imap_delimiter'] = $this->delimiter;
- }
-
-
- /**
- * Get message count for a specific folder
- *
- * @param string $folder Folder name
- * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT]
- * @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
- */
- public function count($folder='', $mode='ALL', $force=false, $status=true)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- return $this->countmessages($folder, $mode, $force, $status);
- }
-
-
- /**
- * protected method for getting nr of messages
- *
- * @param string $folder Folder name
- * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT]
- * @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
- * @see rcube_imap::count()
- */
- protected function countmessages($folder, $mode='ALL', $force=false, $status=true)
- {
- $mode = strtoupper($mode);
-
- // count search set, assume search set is always up-to-date (don't check $force flag)
- if ($this->search_string && $folder == $this->folder && ($mode == 'ALL' || $mode == 'THREADS')) {
- if ($mode == 'ALL') {
- return $this->search_set->count_messages();
- }
- else {
- return $this->search_set->count();
- }
- }
-
- $a_folder_cache = $this->get_cache('messagecount');
-
- // return cached value
- if (!$force && is_array($a_folder_cache[$folder]) && isset($a_folder_cache[$folder][$mode])) {
- return $a_folder_cache[$folder][$mode];
- }
-
- if (!is_array($a_folder_cache[$folder])) {
- $a_folder_cache[$folder] = array();
- }
-
- if ($mode == 'THREADS') {
- $res = $this->fetch_threads($folder, $force);
- $count = $res->count();
-
- if ($status) {
- $msg_count = $res->count_messages();
- $this->set_folder_stats($folder, 'cnt', $msg_count);
- $this->set_folder_stats($folder, 'maxuid', $msg_count ? $this->id2uid($msg_count, $folder) : 0);
- }
- }
- // Need connection here
- else if (!$this->check_connection()) {
- return 0;
- }
- // RECENT count is fetched a bit different
- else if ($mode == 'RECENT') {
- $count = $this->conn->countRecent($folder);
- }
- // use SEARCH for message counting
- else if (!empty($this->options['skip_deleted'])) {
- $search_str = "ALL UNDELETED";
- $keys = array('COUNT');
-
- if ($mode == 'UNSEEN') {
- $search_str .= " UNSEEN";
- }
- else {
- if ($this->messages_caching) {
- $keys[] = 'ALL';
- }
- if ($status) {
- $keys[] = 'MAX';
- }
- }
-
- // @TODO: if $force==false && $mode == 'ALL' we could try to use cache index here
-
- // get message count using (E)SEARCH
- // not very performant but more precise (using UNDELETED)
- $index = $this->conn->search($folder, $search_str, true, $keys);
- $count = $index->count();
-
- if ($mode == 'ALL') {
- // Cache index data, will be used in index_direct()
- $this->icache['undeleted_idx'] = $index;
-
- if ($status) {
- $this->set_folder_stats($folder, 'cnt', $count);
- $this->set_folder_stats($folder, 'maxuid', $index->max());
- }
- }
- }
- else {
- if ($mode == 'UNSEEN') {
- $count = $this->conn->countUnseen($folder);
- }
- else {
- $count = $this->conn->countMessages($folder);
- if ($status) {
- $this->set_folder_stats($folder,'cnt', $count);
- $this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0);
- }
- }
- }
-
- $a_folder_cache[$folder][$mode] = (int)$count;
-
- // write back to cache
- $this->update_cache('messagecount', $a_folder_cache);
-
- return (int)$count;
- }
-
-
- /**
- * 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
- */
- public function list_messages($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- return $this->_list_messages($folder, $page, $sort_field, $sort_order, $slice);
- }
-
-
- /**
- * protected method for listing message 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
- * @see rcube_imap::list_messages
- */
- protected function _list_messages($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
- {
- if (!strlen($folder)) {
- return array();
- }
-
- $this->set_sort_order($sort_field, $sort_order);
- $page = $page ? $page : $this->list_page;
-
- // use saved message set
- if ($this->search_string && $folder == $this->folder) {
- return $this->list_search_messages($folder, $page, $slice);
- }
-
- if ($this->threading) {
- return $this->list_thread_messages($folder, $page, $slice);
- }
-
- // get UIDs of all messages in the folder, sorted
- $index = $this->index($folder, $this->sort_field, $this->sort_order);
-
- if ($index->is_empty()) {
- return array();
- }
-
- $from = ($page-1) * $this->page_size;
- $to = $from + $this->page_size;
-
- $index->slice($from, $to - $from);
-
- if ($slice) {
- $index->slice(-$slice, $slice);
- }
-
- // fetch reqested messages headers
- $a_index = $index->get();
- $a_msg_headers = $this->fetch_headers($folder, $a_index);
-
- return array_values($a_msg_headers);
- }
-
-
- /**
- * protected method for listing message headers using threads
- *
- * @param string $folder Folder name
- * @param int $page Current page to list
- * @param int $slice Number of slice items to extract from result array
- *
- * @return array Indexed array with message header objects
- * @see rcube_imap::list_messages
- */
- protected function list_thread_messages($folder, $page, $slice=0)
- {
- // get all threads (not sorted)
- if ($mcache = $this->get_mcache_engine()) {
- $threads = $mcache->get_thread($folder);
- }
- else {
- $threads = $this->fetch_threads($folder);
- }
-
- return $this->fetch_thread_headers($folder, $threads, $page, $slice);
- }
-
- /**
- * Method for fetching threads data
- *
- * @param string $folder Folder name
- * @param bool $force Use IMAP server, no cache
- *
- * @return rcube_imap_thread Thread data object
- */
- function fetch_threads($folder, $force = false)
- {
- if (!$force && ($mcache = $this->get_mcache_engine())) {
- // don't store in self's internal cache, cache has it's own internal cache
- return $mcache->get_thread($folder);
- }
-
- if (empty($this->icache['threads'])) {
- if (!$this->check_connection()) {
- return new rcube_result_thread();
- }
-
- // get all threads
- $result = $this->conn->thread($folder, $this->threading,
- $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
-
- // add to internal (fast) cache
- $this->icache['threads'] = $result;
- }
-
- return $this->icache['threads'];
- }
-
-
- /**
- * protected method for fetching threaded messages headers
- *
- * @param string $folder Folder name
- * @param rcube_result_thread $threads Threads data object
- * @param int $page List page number
- * @param int $slice Number of threads to slice
- *
- * @return array Messages headers
- */
- protected function fetch_thread_headers($folder, $threads, $page, $slice=0)
- {
- // Sort thread structure
- $this->sort_threads($threads);
-
- $from = ($page-1) * $this->page_size;
- $to = $from + $this->page_size;
-
- $threads->slice($from, $to - $from);
-
- if ($slice) {
- $threads->slice(-$slice, $slice);
- }
-
- // Get UIDs of all messages in all threads
- $a_index = $threads->get();
-
- // fetch reqested headers from server
- $a_msg_headers = $this->fetch_headers($folder, $a_index);
-
- unset($a_index);
-
- // Set depth, has_children and unread_children fields in headers
- $this->set_thread_flags($a_msg_headers, $threads);
-
- return array_values($a_msg_headers);
- }
-
-
- /**
- * protected method for setting threaded messages flags:
- * depth, has_children and unread_children
- *
- * @param array $headers Reference to headers array indexed by message UID
- * @param rcube_result_thread $threads Threads data object
- *
- * @return array Message headers array indexed by message UID
- */
- protected function set_thread_flags(&$headers, $threads)
- {
- $parents = array();
-
- list ($msg_depth, $msg_children) = $threads->get_thread_data();
-
- foreach ($headers as $uid => $header) {
- $depth = $msg_depth[$uid];
- $parents = array_slice($parents, 0, $depth);
-
- if (!empty($parents)) {
- $headers[$uid]->parent_uid = end($parents);
- if (empty($header->flags['SEEN']))
- $headers[$parents[0]]->unread_children++;
- }
- array_push($parents, $uid);
-
- $headers[$uid]->depth = $depth;
- $headers[$uid]->has_children = $msg_children[$uid];
- }
- }
-
-
- /**
- * protected method for listing a set of message headers (search results)
- *
- * @param string $folder Folder name
- * @param int $page Current page to list
- * @param int $slice Number of slice items to extract from result array
- *
- * @return array Indexed array with message header objects
- */
- protected function list_search_messages($folder, $page, $slice=0)
- {
- if (!strlen($folder) || empty($this->search_set) || $this->search_set->is_empty()) {
- return array();
- }
-
- // use saved messages from searching
- if ($this->threading) {
- return $this->list_search_thread_messages($folder, $page, $slice);
- }
-
- // search set is threaded, we need a new one
- if ($this->search_threads) {
- $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
- }
-
- $index = clone $this->search_set;
- $from = ($page-1) * $this->page_size;
- $to = $from + $this->page_size;
-
- // return empty array if no messages found
- if ($index->is_empty()) {
- return array();
- }
-
- // quickest method (default sorting)
- if (!$this->search_sort_field && !$this->sort_field) {
- $got_index = true;
- }
- // sorted messages, so we can first slice array and then fetch only wanted headers
- else if ($this->search_sorted) { // SORT searching result
- $got_index = true;
- // reset search set if sorting field has been changed
- if ($this->sort_field && $this->search_sort_field != $this->sort_field) {
- $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
-
- $index = clone $this->search_set;
-
- // return empty array if no messages found
- if ($index->is_empty()) {
- return array();
- }
- }
- }
-
- if ($got_index) {
- if ($this->sort_order != $index->get_parameters('ORDER')) {
- $index->revert();
- }
-
- // get messages uids for one page
- $index->slice($from, $to-$from);
-
- if ($slice) {
- $index->slice(-$slice, $slice);
- }
-
- // fetch headers
- $a_index = $index->get();
- $a_msg_headers = $this->fetch_headers($folder, $a_index);
-
- return array_values($a_msg_headers);
- }
-
- // SEARCH result, need sorting
- $cnt = $index->count();
-
- // 300: experimantal value for best result
- if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) {
- // use memory less expensive (and quick) method for big result set
- $index = clone $this->index('', $this->sort_field, $this->sort_order);
- // get messages uids for one page...
- $index->slice($start_msg, min($cnt-$from, $this->page_size));
-
- if ($slice) {
- $index->slice(-$slice, $slice);
- }
-
- // ...and fetch headers
- $a_index = $index->get();
- $a_msg_headers = $this->fetch_headers($folder, $a_index);
-
- return array_values($a_msg_headers);
- }
- else {
- // for small result set we can fetch all messages headers
- $a_index = $index->get();
- $a_msg_headers = $this->fetch_headers($folder, $a_index, false);
-
- // return empty array if no messages found
- if (!is_array($a_msg_headers) || empty($a_msg_headers)) {
- return array();
- }
-
- if (!$this->check_connection()) {
- return array();
- }
-
- // if not already sorted
- $a_msg_headers = $this->conn->sortHeaders(
- $a_msg_headers, $this->sort_field, $this->sort_order);
-
- // only return the requested part of the set
- $slice_length = min($this->page_size, $cnt - ($to > $cnt ? $from : $to));
- $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length);
-
- if ($slice) {
- $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice);
- }
-
- return $a_msg_headers;
- }
- }
-
-
- /**
- * protected method for listing a set of threaded message headers (search results)
- *
- * @param string $folder Folder name
- * @param int $page Current page to list
- * @param int $slice Number of slice items to extract from result array
- *
- * @return array Indexed array with message header objects
- * @see rcube_imap::list_search_messages()
- */
- protected function list_search_thread_messages($folder, $page, $slice=0)
- {
- // update search_set if previous data was fetched with disabled threading
- if (!$this->search_threads) {
- if ($this->search_set->is_empty()) {
- return array();
- }
- $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
- }
-
- return $this->fetch_thread_headers($folder, clone $this->search_set, $page, $slice);
- }
-
-
- /**
- * Fetches messages headers (by UID)
- *
- * @param string $folder Folder name
- * @param array $msgs Message UIDs
- * @param bool $sort Enables result sorting by $msgs
- * @param bool $force Disables cache use
- *
- * @return array Messages headers indexed by UID
- */
- function fetch_headers($folder, $msgs, $sort = true, $force = false)
- {
- if (empty($msgs)) {
- return array();
- }
-
- if (!$force && ($mcache = $this->get_mcache_engine())) {
- $headers = $mcache->get_messages($folder, $msgs);
- }
- else if (!$this->check_connection()) {
- return array();
- }
- else {
- // fetch reqested headers from server
- $headers = $this->conn->fetchHeaders(
- $folder, $msgs, true, false, $this->get_fetch_headers());
- }
-
- if (empty($headers)) {
- return array();
- }
-
- foreach ($headers as $h) {
- $a_msg_headers[$h->uid] = $h;
- }
-
- if ($sort) {
- // use this class for message sorting
- $sorter = new rcube_message_header_sorter();
- $sorter->set_index($msgs);
- $sorter->sort_headers($a_msg_headers);
- }
-
- return $a_msg_headers;
- }
-
-
- /**
- * Returns current status of folder
- *
- * We compare the maximum UID to determine the number of
- * new messages because the RECENT flag is not reliable.
- *
- * @param string $folder Folder name
- *
- * @return int Folder status
- */
- public function folder_status($folder = null)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
- $old = $this->get_folder_stats($folder);
-
- // refresh message count -> will update
- $this->countmessages($folder, 'ALL', true);
-
- $result = 0;
-
- if (empty($old)) {
- return $result;
- }
-
- $new = $this->get_folder_stats($folder);
-
- // got new messages
- if ($new['maxuid'] > $old['maxuid']) {
- $result += 1;
- }
- // some messages has been deleted
- if ($new['cnt'] < $old['cnt']) {
- $result += 2;
- }
-
- // @TODO: optional checking for messages flags changes (?)
- // @TODO: UIDVALIDITY checking
-
- return $result;
- }
-
-
- /**
- * Stores folder statistic data in session
- * @TODO: move to separate DB table (cache?)
- *
- * @param string $folder Folder name
- * @param string $name Data name
- * @param mixed $data Data value
- */
- protected function set_folder_stats($folder, $name, $data)
- {
- $_SESSION['folders'][$folder][$name] = $data;
- }
-
-
- /**
- * Gets folder statistic data
- *
- * @param string $folder Folder name
- *
- * @return array Stats data
- */
- protected function get_folder_stats($folder)
- {
- if ($_SESSION['folders'][$folder]) {
- return (array) $_SESSION['folders'][$folder];
- }
-
- return array();
- }
-
-
- /**
- * 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)
- */
- public function index($folder = '', $sort_field = NULL, $sort_order = NULL)
- {
- if ($this->threading) {
- return $this->thread_index($folder, $sort_field, $sort_order);
- }
-
- $this->set_sort_order($sort_field, $sort_order);
-
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- // we have a saved search result, get index from there
- if ($this->search_string) {
- if ($this->search_threads) {
- $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field);
- }
-
- // use message index sort as default sorting
- if (!$this->sort_field || $this->search_sorted) {
- if ($this->sort_field && $this->search_sort_field != $this->sort_field) {
- $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field);
- }
- $index = $this->search_set;
- }
- else if (!$this->check_connection()) {
- return new rcube_result_index();
- }
- else {
- $index = $this->conn->index($folder, $this->search_set->get(),
- $this->sort_field, $this->options['skip_deleted'], true, true);
- }
-
- if ($this->sort_order != $index->get_parameters('ORDER')) {
- $index->revert();
- }
-
- return $index;
- }
-
- // check local cache
- if ($mcache = $this->get_mcache_engine()) {
- $index = $mcache->get_index($folder, $this->sort_field, $this->sort_order);
- }
- // fetch from IMAP server
- else {
- $index = $this->index_direct(
- $folder, $this->sort_field, $this->sort_order);
- }
-
- return $index;
- }
-
-
- /**
- * Return sorted list of message UIDs ignoring current search settings.
- * Doesn't uses cache by default.
- *
- * @param string $folder Folder to get index from
- * @param string $sort_field Sort column
- * @param string $sort_order Sort order [ASC, DESC]
- * @param bool $skip_cache Disables cache usage
- *
- * @return rcube_result_index Sorted list of message UIDs
- */
- public function index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
- {
- if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
- $index = $mcache->get_index($folder, $sort_field, $sort_order);
- }
- // use message index sort as default sorting
- else if (!$sort_field) {
- // use search result from count() if possible
- if ($this->options['skip_deleted'] && !empty($this->icache['undeleted_idx'])
- && $this->icache['undeleted_idx']->get_parameters('ALL') !== null
- && $this->icache['undeleted_idx']->get_parameters('MAILBOX') == $folder
- ) {
- $index = $this->icache['undeleted_idx'];
- }
- else if (!$this->check_connection()) {
- return new rcube_result_index();
- }
- else {
- $index = $this->conn->search($folder,
- 'ALL' .($this->options['skip_deleted'] ? ' UNDELETED' : ''), true);
- }
- }
- else if (!$this->check_connection()) {
- return new rcube_result_index();
- }
- // fetch complete message index
- else {
- if ($this->get_capability('SORT')) {
- $index = $this->conn->sort($folder, $sort_field,
- $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
- }
-
- if (empty($index) || $index->is_error()) {
- $index = $this->conn->index($folder, "1:*", $sort_field,
- $this->options['skip_deleted'], false, true);
- }
- }
-
- if ($sort_order != $index->get_parameters('ORDER')) {
- $index->revert();
- }
-
- return $index;
- }
-
-
- /**
- * Return index of threaded 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_thread Message UIDs
- */
- public function thread_index($folder='', $sort_field=NULL, $sort_order=NULL)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- // we have a saved search result, get index from there
- if ($this->search_string && $this->search_threads && $folder == $this->folder) {
- $threads = $this->search_set;
- }
- else {
- // get all threads (default sort order)
- $threads = $this->fetch_threads($folder);
- }
-
- $this->set_sort_order($sort_field, $sort_order);
- $this->sort_threads($threads);
-
- return $threads;
- }
-
-
- /**
- * Sort threaded result, using THREAD=REFS method
- *
- * @param rcube_result_thread $threads Threads result set
- */
- protected function sort_threads($threads)
- {
- if ($threads->is_empty()) {
- return;
- }
-
- // THREAD=ORDEREDSUBJECT: sorting by sent date of root message
- // THREAD=REFERENCES: sorting by sent date of root message
- // THREAD=REFS: sorting by the most recent date in each thread
-
- if ($this->sort_field && ($this->sort_field != 'date' || $this->get_capability('THREAD') != 'REFS')) {
- $index = $this->index_direct($this->folder, $this->sort_field, $this->sort_order, false);
-
- if (!$index->is_empty()) {
- $threads->sort($index);
- }
- }
- else {
- if ($this->sort_order != $threads->get_parameters('ORDER')) {
- $threads->revert();
- }
- }
- }
-
-
- /**
- * Invoke search request to IMAP 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
- */
- public function search($folder='', $str='ALL', $charset=NULL, $sort_field=NULL)
- {
- if (!$str) {
- $str = 'ALL';
- }
-
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- $results = $this->search_index($folder, $str, $charset, $sort_field);
-
- $this->set_search_set(array($str, $results, $charset, $sort_field,
- $this->threading || $this->search_sorted ? true : false));
- }
-
-
- /**
- * Direct (real and simple) SEARCH request (without result sorting and caching).
- *
- * @param string $mailbox Mailbox name to search in
- * @param string $str Search string
- *
- * @return rcube_result_index Search result (UIDs)
- */
- public function search_once($folder = null, $str = 'ALL')
- {
- if (!$str) {
- return 'ALL';
- }
-
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- if (!$this->check_connection()) {
- return new rcube_result_index();
- }
-
- $index = $this->conn->search($folder, $str, true);
-
- return $index;
- }
-
-
- /**
- * protected search method
- *
- * @param string $folder Folder name
- * @param string $criteria Search criteria
- * @param string $charset Charset
- * @param string $sort_field Sorting field
- *
- * @return rcube_result_index|rcube_result_thread Search results (UIDs)
- * @see rcube_imap::search()
- */
- protected function search_index($folder, $criteria='ALL', $charset=NULL, $sort_field=NULL)
- {
- $orig_criteria = $criteria;
-
- if (!$this->check_connection()) {
- if ($this->threading) {
- return new rcube_result_thread();
- }
- else {
- return new rcube_result_index();
- }
- }
-
- if ($this->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 = $this->conn->thread($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 = $this->conn->thread($folder, $this->threading,
- $this->convert_criteria($criteria, $charset), true, 'US-ASCII');
- }
-
- return $threads;
- }
-
- if ($sort_field && $this->get_capability('SORT')) {
- $charset = $charset ? $charset : $this->default_charset;
- $messages = $this->conn->sort($folder, $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 = $this->conn->sort($folder, $sort_field,
- $this->convert_criteria($criteria, $charset), true, 'US-ASCII');
- }
-
- if (!$messages->is_error()) {
- $this->search_sorted = true;
- return $messages;
- }
- }
-
- $messages = $this->conn->search($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 = $this->conn->search($folder,
- $this->convert_criteria($criteria, $charset), true);
- }
-
- $this->search_sorted = false;
-
- return $messages;
- }
-
-
- /**
- * Converts charset of search criteria string
- *
- * @param string $str Search string
- * @param string $charset Original charset
- * @param string $dest_charset Destination charset (default US-ASCII)
- *
- * @return string Search string
- */
- protected function convert_criteria($str, $charset, $dest_charset='US-ASCII')
- {
- // convert strings to US_ASCII
- if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
- $last = 0; $res = '';
- foreach ($matches[1] as $m) {
- $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
- $string = substr($str, $string_offset - 1, $m[0]);
- $string = rcube_charset::convert($string, $charset, $dest_charset);
- if ($string === false) {
- continue;
- }
- $res .= substr($str, $last, $m[1] - $last - 1) . rcube_imap_generic::escape($string);
- $last = $m[0] + $string_offset - 1;
- }
- if ($last < strlen($str)) {
- $res .= substr($str, $last, strlen($str)-$last);
- }
- }
- // strings for conversion not found
- else {
- $res = $str;
- }
-
- return $res;
- }
-
-
- /**
- * Refresh saved search set
- *
- * @return array Current search set
- */
- public function refresh_search()
- {
- if (!empty($this->search_string)) {
- $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field);
- }
-
- return $this->get_search_set();
- }
-
-
- /**
- * Return message headers object of a specific message
- *
- * @param int $id Message UID
- * @param string $folder Folder to read from
- * @param bool $force True to skip cache
- *
- * @return rcube_message_header Message headers
- */
- public function get_message_headers($uid, $folder = null, $force = false)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- // get cached headers
- if (!$force && $uid && ($mcache = $this->get_mcache_engine())) {
- $headers = $mcache->get_message($folder, $uid);
- }
- else if (!$this->check_connection()) {
- $headers = false;
- }
- else {
- $headers = $this->conn->fetchHeader(
- $folder, $uid, true, true, $this->get_fetch_headers());
- }
-
- return $headers;
- }
-
-
- /**
- * Fetch message headers and body structure from the IMAP server and build
- * an object structure similar to the one generated by PEAR::Mail_mimeDecode
- *
- * @param int $uid Message UID to fetch
- * @param string $folder Folder to read from
- *
- * @return object rcube_message_header Message data
- */
- public function get_message($uid, $folder = null)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- // Check internal cache
- if (!empty($this->icache['message'])) {
- if (($headers = $this->icache['message']) && $headers->uid == $uid) {
- return $headers;
- }
- }
-
- $headers = $this->get_message_headers($uid, $folder);
-
- // message doesn't exist?
- if (empty($headers)) {
- return null;
- }
-
- // structure might be cached
- if (!empty($headers->structure)) {
- return $headers;
- }
-
- $this->msg_uid = $uid;
-
- if (!$this->check_connection()) {
- return $headers;
- }
-
- if (empty($headers->bodystructure)) {
- $headers->bodystructure = $this->conn->getStructure($folder, $uid, true);
- }
-
- $structure = $headers->bodystructure;
-
- if (empty($structure)) {
- return $headers;
- }
-
- // set message charset from message headers
- if ($headers->charset) {
- $this->struct_charset = $headers->charset;
- }
- else {
- $this->struct_charset = $this->structure_charset($structure);
- }
-
- $headers->ctype = strtolower($headers->ctype);
-
- // Here we can recognize malformed BODYSTRUCTURE and
- // 1. [@TODO] parse the message in other way to create our own message structure
- // 2. or just show the raw message body.
- // Example of structure for malformed MIME message:
- // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL)
- if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain'
- && strtolower($structure[0].'/'.$structure[1]) == 'text/plain') {
- // we can handle single-part messages, by simple fix in structure (#1486898)
- if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
- $structure[0] = $m[1];
- $structure[1] = $m[2];
- }
- else {
- // Try to parse the message using Mail_mimeDecode package
- // We need a better solution, Mail_mimeDecode parses message
- // in memory, which wouldn't work for very big messages,
- // (it uses up to 10x more memory than the message size)
- // it's also buggy and not actively developed
- if ($headers->size && rcube_utils::mem_check($headers->size * 10)) {
- $raw_msg = $this->get_raw_body($uid);
- $struct = rcube_mime::parse_message($raw_msg);
- }
- else {
- return $headers;
- }
- }
- }
-
- if (empty($struct)) {
- $struct = $this->structure_part($structure, 0, '', $headers);
- }
-
- // don't trust given content-type
- if (empty($struct->parts) && !empty($headers->ctype)) {
- $struct->mime_id = '1';
- $struct->mimetype = strtolower($headers->ctype);
- list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
- }
-
- $headers->structure = $struct;
-
- return $this->icache['message'] = $headers;
- }
-
-
- /**
- * Build message part object
- *
- * @param array $part
- * @param int $count
- * @param string $parent
- */
- protected function structure_part($part, $count=0, $parent='', $mime_headers=null)
- {
- $struct = new rcube_message_part;
- $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
-
- // multipart
- if (is_array($part[0])) {
- $struct->ctype_primary = 'multipart';
-
- /* RFC3501: BODYSTRUCTURE fields of multipart part
- part1 array
- part2 array
- part3 array
- ....
- 1. subtype
- 2. parameters (optional)
- 3. description (optional)
- 4. language (optional)
- 5. location (optional)
- */
-
- // find first non-array entry
- for ($i=1; $i<count($part); $i++) {
- if (!is_array($part[$i])) {
- $struct->ctype_secondary = strtolower($part[$i]);
- break;
- }
- }
-
- $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
-
- // build parts list for headers pre-fetching
- for ($i=0; $i<count($part); $i++) {
- if (!is_array($part[$i])) {
- break;
- }
- // fetch message headers if message/rfc822
- // or named part (could contain Content-Location header)
- if (!is_array($part[$i][0])) {
- $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
- if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') {
- $mime_part_headers[] = $tmp_part_id;
- }
- else if (in_array('name', (array)$part[$i][2]) && empty($part[$i][3])) {
- $mime_part_headers[] = $tmp_part_id;
- }
- }
- }
-
- // pre-fetch headers of all parts (in one command for better performance)
- // @TODO: we could do this before _structure_part() call, to fetch
- // headers for parts on all levels
- if ($mime_part_headers) {
- $mime_part_headers = $this->conn->fetchMIMEHeaders($this->folder,
- $this->msg_uid, $mime_part_headers);
- }
-
- $struct->parts = array();
- for ($i=0, $count=0; $i<count($part); $i++) {
- if (!is_array($part[$i])) {
- break;
- }
- $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
- $struct->parts[] = $this->structure_part($part[$i], ++$count, $struct->mime_id,
- $mime_part_headers[$tmp_part_id]);
- }
-
- return $struct;
- }
-
- /* RFC3501: BODYSTRUCTURE fields of non-multipart part
- 0. type
- 1. subtype
- 2. parameters
- 3. id
- 4. description
- 5. encoding
- 6. size
- -- text
- 7. lines
- -- message/rfc822
- 7. envelope structure
- 8. body structure
- 9. lines
- --
- x. md5 (optional)
- x. disposition (optional)
- x. language (optional)
- x. location (optional)
- */
-
- // regular part
- $struct->ctype_primary = strtolower($part[0]);
- $struct->ctype_secondary = strtolower($part[1]);
- $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary;
-
- // read content type parameters
- if (is_array($part[2])) {
- $struct->ctype_parameters = array();
- for ($i=0; $i<count($part[2]); $i+=2) {
- $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
- }
-
- if (isset($struct->ctype_parameters['charset'])) {
- $struct->charset = $struct->ctype_parameters['charset'];
- }
- }
-
- // #1487700: workaround for lack of charset in malformed structure
- if (empty($struct->charset) && !empty($mime_headers) && $mime_headers->charset) {
- $struct->charset = $mime_headers->charset;
- }
-
- // read content encoding
- if (!empty($part[5])) {
- $struct->encoding = strtolower($part[5]);
- $struct->headers['content-transfer-encoding'] = $struct->encoding;
- }
-
- // get part size
- if (!empty($part[6])) {
- $struct->size = intval($part[6]);
- }
-
- // read part disposition
- $di = 8;
- if ($struct->ctype_primary == 'text') {
- $di += 1;
- }
- else if ($struct->mimetype == 'message/rfc822') {
- $di += 3;
- }
-
- if (is_array($part[$di]) && count($part[$di]) == 2) {
- $struct->disposition = strtolower($part[$di][0]);
-
- if (is_array($part[$di][1])) {
- for ($n=0; $n<count($part[$di][1]); $n+=2) {
- $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
- }
- }
- }
-
- // get message/rfc822's child-parts
- if (is_array($part[8]) && $di != 8) {
- $struct->parts = array();
- for ($i=0, $count=0; $i<count($part[8]); $i++) {
- if (!is_array($part[8][$i])) {
- break;
- }
- $struct->parts[] = $this->structure_part($part[8][$i], ++$count, $struct->mime_id);
- }
- }
-
- // get part ID
- if (!empty($part[3])) {
- $struct->content_id = $part[3];
- $struct->headers['content-id'] = $part[3];
-
- if (empty($struct->disposition)) {
- $struct->disposition = 'inline';
- }
- }
-
- // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
- if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) {
- if (empty($mime_headers)) {
- $mime_headers = $this->conn->fetchPartHeader(
- $this->folder, $this->msg_uid, true, $struct->mime_id);
- }
-
- if (is_string($mime_headers)) {
- $struct->headers = rcube_mime::parse_headers($mime_headers) + $struct->headers;
- }
- else if (is_object($mime_headers)) {
- $struct->headers = get_object_vars($mime_headers) + $struct->headers;
- }
-
- // get real content-type of message/rfc822
- if ($struct->mimetype == 'message/rfc822') {
- // single-part
- if (!is_array($part[8][0])) {
- $struct->real_mimetype = strtolower($part[8][0] . '/' . $part[8][1]);
- }
- // multi-part
- else {
- for ($n=0; $n<count($part[8]); $n++) {
- if (!is_array($part[8][$n])) {
- break;
- }
- }
- $struct->real_mimetype = 'multipart/' . strtolower($part[8][$n]);
- }
- }
-
- if ($struct->ctype_primary == 'message' && empty($struct->parts)) {
- if (is_array($part[8]) && $di != 8) {
- $struct->parts[] = $this->structure_part($part[8], ++$count, $struct->mime_id);
- }
- }
- }
-
- // normalize filename property
- $this->set_part_filename($struct, $mime_headers);
-
- return $struct;
- }
-
-
- /**
- * Set attachment filename from message part structure
- *
- * @param rcube_message_part $part Part object
- * @param string $headers Part's raw headers
- */
- protected function set_part_filename(&$part, $headers=null)
- {
- if (!empty($part->d_parameters['filename'])) {
- $filename_mime = $part->d_parameters['filename'];
- }
- else if (!empty($part->d_parameters['filename*'])) {
- $filename_encoded = $part->d_parameters['filename*'];
- }
- else if (!empty($part->ctype_parameters['name*'])) {
- $filename_encoded = $part->ctype_parameters['name*'];
- }
- // RFC2231 value continuations
- // TODO: this should be rewrited to support RFC2231 4.1 combinations
- else if (!empty($part->d_parameters['filename*0'])) {
- $i = 0;
- while (isset($part->d_parameters['filename*'.$i])) {
- $filename_mime .= $part->d_parameters['filename*'.$i];
- $i++;
- }
- // some servers (eg. dovecot-1.x) have no support for parameter value continuations
- // we must fetch and parse headers "manually"
- if ($i<2) {
- if (!$headers) {
- $headers = $this->conn->fetchPartHeader(
- $this->folder, $this->msg_uid, true, $part->mime_id);
- }
- $filename_mime = '';
- $i = 0;
- while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
- $filename_mime .= $matches[1];
- $i++;
- }
- }
- }
- else if (!empty($part->d_parameters['filename*0*'])) {
- $i = 0;
- while (isset($part->d_parameters['filename*'.$i.'*'])) {
- $filename_encoded .= $part->d_parameters['filename*'.$i.'*'];
- $i++;
- }
- if ($i<2) {
- if (!$headers) {
- $headers = $this->conn->fetchPartHeader(
- $this->folder, $this->msg_uid, true, $part->mime_id);
- }
- $filename_encoded = '';
- $i = 0; $matches = array();
- while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
- $filename_encoded .= $matches[1];
- $i++;
- }
- }
- }
- else if (!empty($part->ctype_parameters['name*0'])) {
- $i = 0;
- while (isset($part->ctype_parameters['name*'.$i])) {
- $filename_mime .= $part->ctype_parameters['name*'.$i];
- $i++;
- }
- if ($i<2) {
- if (!$headers) {
- $headers = $this->conn->fetchPartHeader(
- $this->folder, $this->msg_uid, true, $part->mime_id);
- }
- $filename_mime = '';
- $i = 0; $matches = array();
- while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
- $filename_mime .= $matches[1];
- $i++;
- }
- }
- }
- else if (!empty($part->ctype_parameters['name*0*'])) {
- $i = 0;
- while (isset($part->ctype_parameters['name*'.$i.'*'])) {
- $filename_encoded .= $part->ctype_parameters['name*'.$i.'*'];
- $i++;
- }
- if ($i<2) {
- if (!$headers) {
- $headers = $this->conn->fetchPartHeader(
- $this->folder, $this->msg_uid, true, $part->mime_id);
- }
- $filename_encoded = '';
- $i = 0; $matches = array();
- while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
- $filename_encoded .= $matches[1];
- $i++;
- }
- }
- }
- // read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird)
- else if (!empty($part->ctype_parameters['name'])) {
- $filename_mime = $part->ctype_parameters['name'];
- }
- // Content-Disposition
- else if (!empty($part->headers['content-description'])) {
- $filename_mime = $part->headers['content-description'];
- }
- else {
- return;
- }
-
- // decode filename
- if (!empty($filename_mime)) {
- if (!empty($part->charset)) {
- $charset = $part->charset;
- }
- else if (!empty($this->struct_charset)) {
- $charset = $this->struct_charset;
- }
- else {
- $charset = rcube_charset::detect($filename_mime, $this->default_charset);
- }
-
- $part->filename = rcube_mime::decode_mime_string($filename_mime, $charset);
- }
- else if (!empty($filename_encoded)) {
- // decode filename according to RFC 2231, Section 4
- if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) {
- $filename_charset = $fmatches[1];
- $filename_encoded = $fmatches[2];
- }
-
- $part->filename = rcube_charset::convert(urldecode($filename_encoded), $filename_charset);
- }
- }
-
-
- /**
- * Get charset name from message structure (first part)
- *
- * @param array $structure Message structure
- *
- * @return string Charset name
- */
- protected function structure_charset($structure)
- {
- while (is_array($structure)) {
- if (is_array($structure[2]) && $structure[2][0] == 'charset') {
- return $structure[2][1];
- }
- $structure = $structure[0];
- }
- }
-
-
- /**
- * 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
- */
- public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false)
- {
- if (!$this->check_connection()) {
- return null;
- }
-
- // get part data if not provided
- if (!is_object($o_part)) {
- $structure = $this->conn->getStructure($this->folder, $uid, true);
- $part_data = rcube_imap_generic::getStructurePartData($structure, $part);
-
- $o_part = new rcube_message_part;
- $o_part->ctype_primary = $part_data['type'];
- $o_part->encoding = $part_data['encoding'];
- $o_part->charset = $part_data['charset'];
- $o_part->size = $part_data['size'];
- }
-
- if ($o_part && $o_part->size) {
- $body = $this->conn->handlePartBody($this->folder, $uid, true,
- $part ? $part : 'TEXT', $o_part->encoding, $print, $fp);
- }
-
- if ($fp || $print) {
- return true;
- }
-
- // convert charset (if text or message part)
- if ($body && preg_match('/^(text|message)$/', $o_part->ctype_primary)) {
- // Remove NULL characters if any (#1486189)
- if (strpos($body, "\x00") !== false) {
- $body = str_replace("\x00", '', $body);
- }
-
- if (!$skip_charset_conv) {
- if (!$o_part->charset || strtoupper($o_part->charset) == 'US-ASCII') {
- // try to extract charset information from HTML meta tag (#1488125)
- if ($o_part->ctype_secondary == 'html' && preg_match('/<meta[^>]+charset=([a-z0-9-_]+)/i', $body, $m)) {
- $o_part->charset = strtoupper($m[1]);
- }
- else {
- $o_part->charset = $this->default_charset;
- }
- }
- $body = rcube_charset::convert($body, $o_part->charset);
- }
- }
-
- return $body;
- }
-
-
- /**
- * 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
- *
- * @return string Message source string
- */
- public function get_raw_body($uid, $fp=null)
- {
- if (!$this->check_connection()) {
- return null;
- }
-
- return $this->conn->handlePartBody($this->folder, $uid,
- true, null, null, false, $fp);
- }
-
-
- /**
- * Returns the message headers as string
- *
- * @param int $uid Message UID
- *
- * @return string Message headers string
- */
- public function get_raw_headers($uid)
- {
- if (!$this->check_connection()) {
- return null;
- }
-
- return $this->conn->fetchPartHeader($this->folder, $uid, true);
- }
-
-
- /**
- * Sends the whole message source to stdout
- *
- * @param int $uid Message UID
- * @param bool $formatted Enables line-ending formatting
- */
- public function print_raw_body($uid, $formatted = true)
- {
- if (!$this->check_connection()) {
- return;
- }
-
- $this->conn->handlePartBody($this->folder, $uid, true, null, null, true, null, $formatted);
- }
-
-
- /**
- * 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 boolean Operation status
- */
- public function set_flag($uids, $flag, $folder=null, $skip_cache=false)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- $flag = strtoupper($flag);
- list($uids, $all_mode) = $this->parse_uids($uids);
-
- if (strpos($flag, 'UN') === 0) {
- $result = $this->conn->unflag($folder, $uids, substr($flag, 2));
- }
- else {
- $result = $this->conn->flag($folder, $uids, $flag);
- }
-
- if ($result) {
- // reload message headers if cached
- // @TODO: update flags instead removing from cache
- if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
- $status = strpos($flag, 'UN') !== 0;
- $mflag = preg_replace('/^UN/', '', $flag);
- $mcache->change_flag($folder, $all_mode ? null : explode(',', $uids),
- $mflag, $status);
- }
-
- // clear cached counters
- if ($flag == 'SEEN' || $flag == 'UNSEEN') {
- $this->clear_messagecount($folder, 'SEEN');
- $this->clear_messagecount($folder, 'UNSEEN');
- }
- else if ($flag == 'DELETED') {
- $this->clear_messagecount($folder, 'DELETED');
- }
- }
-
- return $result;
- }
-
-
- /**
- * Append a mail message (source) to a specific folder
- *
- * @param string $folder Target folder
- * @param string $message The message source string or filename
- * @param string $headers Headers string if $message contains only the body
- * @param boolean $is_file True if $message is a filename
- * @param array $flags Message flags
- * @param mixed $date Message internal date
- *
- * @return int|bool Appended message UID or True on success, False on error
- */
- public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- // make sure folder exists
- if (!$this->folder_exists($folder)) {
- return false;
- }
-
- $date = $this->date_format($date);
-
- if ($is_file) {
- $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date);
- }
- else {
- $saved = $this->conn->append($folder, $message, $flags, $date);
- }
-
- if ($saved) {
- // increase messagecount of the target folder
- $this->set_messagecount($folder, 'ALL', 1);
- }
-
- return $saved;
- }
-
-
- /**
- * Move a message from one folder to another
- *
- * @param mixed $uids Message UIDs as array or comma-separated string, or '*'
- * @param string $to_mbox Target folder
- * @param string $from_mbox Source folder
- *
- * @return boolean True on success, False on error
- */
- public function move_message($uids, $to_mbox, $from_mbox='')
- {
- if (!strlen($from_mbox)) {
- $from_mbox = $this->folder;
- }
-
- if ($to_mbox === $from_mbox) {
- return false;
- }
-
- list($uids, $all_mode) = $this->parse_uids($uids);
-
- // exit if no message uids are specified
- if (empty($uids)) {
- return false;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- // make sure folder exists
- if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) {
- if (in_array($to_mbox, $this->default_folders)) {
- if (!$this->create_folder($to_mbox, true)) {
- return false;
- }
- }
- else {
- return false;
- }
- }
-
- $config = rcube::get_instance()->config;
- $to_trash = $to_mbox == $config->get('trash_mbox');
-
- // flag messages as read before moving them
- if ($to_trash && $config->get('read_when_deleted')) {
- // don't flush cache (4th argument)
- $this->set_flag($uids, 'SEEN', $from_mbox, true);
- }
-
- // move messages
- $moved = $this->conn->move($uids, $from_mbox, $to_mbox);
-
- // send expunge command in order to have the moved message
- // really deleted from the source folder
- if ($moved) {
- $this->expunge_message($uids, $from_mbox, false);
- $this->clear_messagecount($from_mbox);
- $this->clear_messagecount($to_mbox);
- }
- // moving failed
- else if ($to_trash && $config->get('delete_always', false)) {
- $moved = $this->delete_message($uids, $from_mbox);
- }
-
- if ($moved) {
- // unset threads internal cache
- unset($this->icache['threads']);
-
- // remove message ids from search set
- if ($this->search_set && $from_mbox == $this->folder) {
- // threads are too complicated to just remove messages from set
- if ($this->search_threads || $all_mode) {
- $this->refresh_search();
- }
- else {
- $this->search_set->filter(explode(',', $uids));
- }
- }
-
- // remove cached messages
- // @TODO: do cache update instead of clearing it
- $this->clear_message_cache($from_mbox, $all_mode ? null : explode(',', $uids));
- }
-
- return $moved;
- }
-
-
- /**
- * Copy a message from one folder to another
- *
- * @param mixed $uids Message UIDs as array or comma-separated string, or '*'
- * @param string $to_mbox Target folder
- * @param string $from_mbox Source folder
- *
- * @return boolean True on success, False on error
- */
- public function copy_message($uids, $to_mbox, $from_mbox='')
- {
- if (!strlen($from_mbox)) {
- $from_mbox = $this->folder;
- }
-
- list($uids, $all_mode) = $this->parse_uids($uids);
-
- // exit if no message uids are specified
- if (empty($uids)) {
- return false;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- // make sure folder exists
- if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) {
- if (in_array($to_mbox, $this->default_folders)) {
- if (!$this->create_folder($to_mbox, true)) {
- return false;
- }
- }
- else {
- return false;
- }
- }
-
- // copy messages
- $copied = $this->conn->copy($uids, $from_mbox, $to_mbox);
-
- if ($copied) {
- $this->clear_messagecount($to_mbox);
- }
-
- return $copied;
- }
-
-
- /**
- * Mark messages as deleted and expunge them
- *
- * @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
- */
- public function delete_message($uids, $folder='')
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- list($uids, $all_mode) = $this->parse_uids($uids);
-
- // exit if no message uids are specified
- if (empty($uids)) {
- return false;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- $deleted = $this->conn->flag($folder, $uids, 'DELETED');
-
- if ($deleted) {
- // send expunge command in order to have the deleted message
- // really deleted from the folder
- $this->expunge_message($uids, $folder, false);
- $this->clear_messagecount($folder);
- unset($this->uid_id_map[$folder]);
-
- // unset threads internal cache
- unset($this->icache['threads']);
-
- // remove message ids from search set
- if ($this->search_set && $folder == $this->folder) {
- // threads are too complicated to just remove messages from set
- if ($this->search_threads || $all_mode) {
- $this->refresh_search();
- }
- else {
- $this->search_set->filter(explode(',', $uids));
- }
- }
-
- // remove cached messages
- $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
- }
-
- return $deleted;
- }
-
-
- /**
- * Send IMAP expunge command and clear 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 failure
- */
- public function expunge_message($uids, $folder = null, $clear_cache = true)
- {
- if ($uids && $this->get_capability('UIDPLUS')) {
- list($uids, $all_mode) = $this->parse_uids($uids);
- }
- else {
- $uids = null;
- }
-
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- // force folder selection and check if folder is writeable
- // to prevent a situation when CLOSE is executed on closed
- // or EXPUNGE on read-only folder
- $result = $this->conn->select($folder);
- if (!$result) {
- return false;
- }
-
- if (!$this->conn->data['READ-WRITE']) {
- $this->conn->setError(rcube_imap_generic::ERROR_READONLY, "Folder is read-only");
- return false;
- }
-
- // CLOSE(+SELECT) should be faster than EXPUNGE
- if (empty($uids) || $all_mode) {
- $result = $this->conn->close();
- }
- else {
- $result = $this->conn->expunge($folder, $uids);
- }
-
- if ($result && $clear_cache) {
- $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
- $this->clear_messagecount($folder);
- }
-
- return $result;
- }
-
-
- /* --------------------------------
- * folder managment
- * --------------------------------*/
-
- /**
- * Public method for listing 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
- */
- public function list_folders_subscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
- {
- $cache_key = $root.':'.$name;
- if (!empty($filter)) {
- $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
- }
- $cache_key .= ':'.$rights;
- $cache_key = 'mailboxes.'.md5($cache_key);
-
- // get cached folder list
- $a_mboxes = $this->get_cache($cache_key);
- if (is_array($a_mboxes)) {
- return $a_mboxes;
- }
-
- // Give plugins a chance to provide a list of folders
- $data = rcube::get_instance()->plugins->exec_hook('storage_folders',
- array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB'));
-
- if (isset($data['folders'])) {
- $a_mboxes = $data['folders'];
- }
- else {
- $a_mboxes = $this->list_folders_subscribed_direct($root, $name);
- }
-
- if (!is_array($a_mboxes)) {
- return array();
- }
-
- // filter folders list according to rights requirements
- if ($rights && $this->get_capability('ACL')) {
- $a_mboxes = $this->filter_rights($a_mboxes, $rights);
- }
-
- // INBOX should always be available
- if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
- array_unshift($a_mboxes, 'INBOX');
- }
-
- // sort folders (always sort for cache)
- if (!$skip_sort || $this->cache) {
- $a_mboxes = $this->sort_folder_list($a_mboxes);
- }
-
- // write folders list to cache
- $this->update_cache($cache_key, $a_mboxes);
-
- return $a_mboxes;
- }
-
-
- /**
- * Method for direct folders listing (LSUB)
- *
- * @param string $root Optional root folder
- * @param string $name Optional name pattern
- *
- * @return array List of subscribed folders
- * @see rcube_imap::list_folders_subscribed()
- */
- public function list_folders_subscribed_direct($root='', $name='*')
- {
- if (!$this->check_connection()) {
- return null;
- }
-
- $config = rcube::get_instance()->config;
-
- // Server supports LIST-EXTENDED, we can use selection options
- // #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED
- $list_extended = !$config->get('imap_force_lsub') && $this->get_capability('LIST-EXTENDED');
- if ($list_extended) {
- // This will also set folder options, LSUB doesn't do that
- $a_folders = $this->conn->listMailboxes($root, $name,
- NULL, array('SUBSCRIBED'));
- }
- else {
- // retrieve list of folders from IMAP server using LSUB
- $a_folders = $this->conn->listSubscribed($root, $name);
- }
-
- if (!is_array($a_folders)) {
- return array();
- }
-
- // #1486796: some server configurations doesn't return folders in all namespaces
- if ($root == '' && $name == '*' && $config->get('imap_force_ns')) {
- $this->list_folders_update($a_folders, ($list_extended ? 'ext-' : '') . 'subscribed');
- }
-
- if ($list_extended) {
- // unsubscribe non-existent folders, remove from the list
- // we can do this only when LIST response is available
- if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) {
- foreach ($a_folders as $idx => $folder) {
- if (($opts = $this->conn->data['LIST'][$folder])
- && in_array('\\NonExistent', $opts)
- ) {
- $this->conn->unsubscribe($folder);
- unset($a_folders[$idx]);
- }
- }
- }
- }
- else {
- // unsubscribe non-existent folders, remove them from the list,
- // we can do this only when LIST response is available
- if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) {
- foreach ($a_folders as $idx => $folder) {
- if (!isset($this->conn->data['LIST'][$folder])
- || in_array('\\Noselect', $this->conn->data['LIST'][$folder])
- ) {
- // Some servers returns \Noselect for existing folders
- if (!$this->folder_exists($folder)) {
- $this->conn->unsubscribe($folder);
- unset($a_folders[$idx]);
- }
- }
- }
- }
- }
-
- return $a_folders;
- }
-
-
- /**
- * 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
- */
- public function list_folders($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
- {
- $cache_key = $root.':'.$name;
- if (!empty($filter)) {
- $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
- }
- $cache_key .= ':'.$rights;
- $cache_key = 'mailboxes.list.'.md5($cache_key);
-
- // get cached folder list
- $a_mboxes = $this->get_cache($cache_key);
- if (is_array($a_mboxes)) {
- return $a_mboxes;
- }
-
- // Give plugins a chance to provide a list of folders
- $data = rcube::get_instance()->plugins->exec_hook('storage_folders',
- array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST'));
-
- if (isset($data['folders'])) {
- $a_mboxes = $data['folders'];
- }
- else {
- // retrieve list of folders from IMAP server
- $a_mboxes = $this->list_folders_direct($root, $name);
- }
-
- if (!is_array($a_mboxes)) {
- $a_mboxes = array();
- }
-
- // INBOX should always be available
- if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
- array_unshift($a_mboxes, 'INBOX');
- }
-
- // cache folder attributes
- if ($root == '' && $name == '*' && empty($filter) && !empty($this->conn->data)) {
- $this->update_cache('mailboxes.attributes', $this->conn->data['LIST']);
- }
-
- // filter folders list according to rights requirements
- if ($rights && $this->get_capability('ACL')) {
- $a_folders = $this->filter_rights($a_folders, $rights);
- }
-
- // filter folders and sort them
- if (!$skip_sort) {
- $a_mboxes = $this->sort_folder_list($a_mboxes);
- }
-
- // write folders list to cache
- $this->update_cache($cache_key, $a_mboxes);
-
- return $a_mboxes;
- }
-
-
- /**
- * Method for direct folders listing (LIST)
- *
- * @param string $root Optional root folder
- * @param string $name Optional name pattern
- *
- * @return array List of folders
- * @see rcube_imap::list_folders()
- */
- public function list_folders_direct($root='', $name='*')
- {
- if (!$this->check_connection()) {
- return null;
- }
-
- $result = $this->conn->listMailboxes($root, $name);
-
- if (!is_array($result)) {
- return array();
- }
-
- $config = rcube::get_instance()->config;
-
- // #1486796: some server configurations doesn't return folders in all namespaces
- if ($root == '' && $name == '*' && $config->get('imap_force_ns')) {
- $this->list_folders_update($result);
- }
-
- return $result;
- }
-
-
- /**
- * Fix folders list by adding folders from other namespaces.
- * Needed on some servers eg. Courier IMAP
- *
- * @param array $result Reference to folders list
- * @param string $type Listing type (ext-subscribed, subscribed or all)
- */
- private function list_folders_update(&$result, $type = null)
- {
- $delim = $this->get_hierarchy_delimiter();
- $namespace = $this->get_namespace();
- $search = array();
-
- // build list of namespace prefixes
- foreach ((array)$namespace as $ns) {
- if (is_array($ns)) {
- foreach ($ns as $ns_data) {
- if (strlen($ns_data[0])) {
- $search[] = $ns_data[0];
- }
- }
- }
- }
-
- if (!empty($search)) {
- // go through all folders detecting namespace usage
- foreach ($result as $folder) {
- foreach ($search as $idx => $prefix) {
- if (strpos($folder, $prefix) === 0) {
- unset($search[$idx]);
- }
- }
- if (empty($search)) {
- break;
- }
- }
-
- // get folders in hidden namespaces and add to the result
- foreach ($search as $prefix) {
- if ($type == 'ext-subscribed') {
- $list = $this->conn->listMailboxes('', $prefix . '*', null, array('SUBSCRIBED'));
- }
- else if ($type == 'subscribed') {
- $list = $this->conn->listSubscribed('', $prefix . '*');
- }
- else {
- $list = $this->conn->listMailboxes('', $prefix . '*');
- }
-
- if (!empty($list)) {
- $result = array_merge($result, $list);
- }
- }
- }
- }
-
-
- /**
- * Filter the given list of folders according to access rights
- */
- protected function filter_rights($a_folders, $rights)
- {
- $regex = '/('.$rights.')/';
- foreach ($a_folders as $idx => $folder) {
- $myrights = join('', (array)$this->my_rights($folder));
- if ($myrights !== null && !preg_match($regex, $myrights)) {
- unset($a_folders[$idx]);
- }
- }
-
- return $a_folders;
- }
-
-
- /**
- * Get mailbox quota information
- * added by Nuny
- *
- * @return mixed Quota info or False if not supported
- */
- public function get_quota()
- {
- if ($this->get_capability('QUOTA') && $this->check_connection()) {
- return $this->conn->getQuota();
- }
-
- return 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
- */
- public function folder_size($folder)
- {
- if (!$this->check_connection()) {
- return 0;
- }
-
- // @TODO: could we try to use QUOTA here?
- $result = $this->conn->fetchHeaderIndex($folder, '1:*', 'SIZE', false);
-
- if (is_array($result)) {
- $result = array_sum($result);
- }
-
- return $result;
- }
-
-
- /**
- * Subscribe to a specific folder(s)
- *
- * @param array $folders Folder name(s)
- *
- * @return boolean True on success
- */
- public function subscribe($folders)
- {
- // let this common function do the main work
- return $this->change_subscription($folders, 'subscribe');
- }
-
-
- /**
- * Unsubscribe folder(s)
- *
- * @param array $a_mboxes Folder name(s)
- *
- * @return boolean True on success
- */
- public function unsubscribe($folders)
- {
- // let this common function do the main work
- return $this->change_subscription($folders, 'unsubscribe');
- }
-
-
- /**
- * Create a new folder on the server and register it in local cache
- *
- * @param string $folder New folder name
- * @param boolean $subscribe True if the new folder should be subscribed
- *
- * @return boolean True on success
- */
- public function create_folder($folder, $subscribe=false)
- {
- if (!$this->check_connection()) {
- return false;
- }
-
- $result = $this->conn->createFolder($folder);
-
- // try to subscribe it
- if ($result) {
- // clear cache
- $this->clear_cache('mailboxes', true);
-
- if ($subscribe) {
- $this->subscribe($folder);
- }
- }
-
- return $result;
- }
-
-
- /**
- * 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
- */
- public function rename_folder($folder, $new_name)
- {
- if (!strlen($new_name)) {
- return false;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- $delm = $this->get_hierarchy_delimiter();
-
- // get list of subscribed folders
- if ((strpos($folder, '%') === false) && (strpos($folder, '*') === false)) {
- $a_subscribed = $this->list_folders_subscribed('', $folder . $delm . '*');
- $subscribed = $this->folder_exists($folder, true);
- }
- else {
- $a_subscribed = $this->list_folders_subscribed();
- $subscribed = in_array($folder, $a_subscribed);
- }
-
- $result = $this->conn->renameFolder($folder, $new_name);
-
- if ($result) {
- // unsubscribe the old folder, subscribe the new one
- if ($subscribed) {
- $this->conn->unsubscribe($folder);
- $this->conn->subscribe($new_name);
- }
-
- // check if folder children are subscribed
- foreach ($a_subscribed as $c_subscribed) {
- if (strpos($c_subscribed, $folder.$delm) === 0) {
- $this->conn->unsubscribe($c_subscribed);
- $this->conn->subscribe(preg_replace('/^'.preg_quote($folder, '/').'/',
- $new_name, $c_subscribed));
-
- // clear cache
- $this->clear_message_cache($c_subscribed);
- }
- }
-
- // clear cache
- $this->clear_message_cache($folder);
- $this->clear_cache('mailboxes', true);
- }
-
- return $result;
- }
-
-
- /**
- * Remove folder from server
- *
- * @param string $folder Folder name
- *
- * @return boolean True on success
- */
- function delete_folder($folder)
- {
- $delm = $this->get_hierarchy_delimiter();
-
- if (!$this->check_connection()) {
- return false;
- }
-
- // get list of folders
- if ((strpos($folder, '%') === false) && (strpos($folder, '*') === false)) {
- $sub_mboxes = $this->list_folders('', $folder . $delm . '*');
- }
- else {
- $sub_mboxes = $this->list_folders();
- }
-
- // send delete command to server
- $result = $this->conn->deleteFolder($folder);
-
- if ($result) {
- // unsubscribe folder
- $this->conn->unsubscribe($folder);
-
- foreach ($sub_mboxes as $c_mbox) {
- if (strpos($c_mbox, $folder.$delm) === 0) {
- $this->conn->unsubscribe($c_mbox);
- if ($this->conn->deleteFolder($c_mbox)) {
- $this->clear_message_cache($c_mbox);
- }
- }
- }
-
- // clear folder-related cache
- $this->clear_message_cache($folder);
- $this->clear_cache('mailboxes', true);
- }
-
- return $result;
- }
-
-
- /**
- * Create all folders specified as default
- */
- public function create_default_folders()
- {
- // create default folders if they do not exist
- foreach ($this->default_folders as $folder) {
- if (!$this->folder_exists($folder)) {
- $this->create_folder($folder, true);
- }
- else if (!$this->folder_exists($folder, true)) {
- $this->subscribe($folder);
- }
- }
- }
-
-
- /**
- * Checks if folder exists and is subscribed
- *
- * @param string $folder Folder name
- * @param boolean $subscription Enable subscription checking
- *
- * @return boolean TRUE or FALSE
- */
- public function folder_exists($folder, $subscription=false)
- {
- if ($folder == 'INBOX') {
- return true;
- }
-
- $key = $subscription ? 'subscribed' : 'existing';
-
- if (is_array($this->icache[$key]) && in_array($folder, $this->icache[$key])) {
- return true;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- if ($subscription) {
- $a_folders = $this->conn->listSubscribed('', $folder);
- }
- else {
- $a_folders = $this->conn->listMailboxes('', $folder);
- }
-
- if (is_array($a_folders) && in_array($folder, $a_folders)) {
- $this->icache[$key][] = $folder;
- return true;
- }
-
- return false;
- }
-
-
- /**
- * Returns the namespace where the folder is in
- *
- * @param string $folder Folder name
- *
- * @return string One of 'personal', 'other' or 'shared'
- */
- public function folder_namespace($folder)
- {
- if ($folder == 'INBOX') {
- return 'personal';
- }
-
- foreach ($this->namespace as $type => $namespace) {
- if (is_array($namespace)) {
- foreach ($namespace as $ns) {
- if ($len = strlen($ns[0])) {
- if (($len > 1 && $folder == substr($ns[0], 0, -1))
- || strpos($folder, $ns[0]) === 0
- ) {
- return $type;
- }
- }
- }
- }
- }
-
- return 'personal';
- }
-
-
- /**
- * 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
- */
- public function mod_folder($folder, $mode = 'out')
- {
- if (!strlen($folder)) {
- return $folder;
- }
-
- $prefix = $this->namespace['prefix']; // see set_env()
- $prefix_len = strlen($prefix);
-
- if (!$prefix_len) {
- return $folder;
- }
-
- // remove prefix for output
- if ($mode == 'out') {
- if (substr($folder, 0, $prefix_len) === $prefix) {
- return substr($folder, $prefix_len);
- }
- }
- // add prefix for input (e.g. folder creation)
- else {
- return $prefix . $folder;
- }
-
- return $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
- */
- public function folder_attributes($folder, $force=false)
- {
- // get attributes directly from LIST command
- if (!empty($this->conn->data['LIST']) && is_array($this->conn->data['LIST'][$folder])) {
- $opts = $this->conn->data['LIST'][$folder];
- }
- // get cached folder attributes
- else if (!$force) {
- $opts = $this->get_cache('mailboxes.attributes');
- $opts = $opts[$folder];
- }
-
- if (!is_array($opts)) {
- if (!$this->check_connection()) {
- return array();
- }
-
- $this->conn->listMailboxes('', $folder);
- $opts = $this->conn->data['LIST'][$folder];
- }
-
- return is_array($opts) ? $opts : array();
- }
-
-
- /**
- * Gets connection (and current folder) data: UIDVALIDITY, EXISTS, RECENT,
- * PERMANENTFLAGS, UIDNEXT, UNSEEN
- *
- * @param string $folder Folder name
- *
- * @return array Data
- */
- public function folder_data($folder)
- {
- if (!strlen($folder)) {
- $folder = $this->folder !== null ? $this->folder : 'INBOX';
- }
-
- if ($this->conn->selected != $folder) {
- if (!$this->check_connection()) {
- return array();
- }
-
- if ($this->conn->select($folder)) {
- $this->folder = $folder;
- }
- else {
- return null;
- }
- }
-
- $data = $this->conn->data;
-
- // add (E)SEARCH result for ALL UNDELETED query
- if (!empty($this->icache['undeleted_idx'])
- && $this->icache['undeleted_idx']->get_parameters('MAILBOX') == $folder
- ) {
- $data['UNDELETED'] = $this->icache['undeleted_idx'];
- }
-
- return $data;
- }
-
-
- /**
- * Returns extended information about the folder
- *
- * @param string $folder Folder name
- *
- * @return array Data
- */
- public function folder_info($folder)
- {
- if ($this->icache['options'] && $this->icache['options']['name'] == $folder) {
- return $this->icache['options'];
- }
-
- // get cached metadata
- $cache_key = 'mailboxes.folder-info.' . $folder;
- $cached = $this->get_cache($cache_key);
-
- if (is_array($cached)) {
- return $cached;
- }
-
- $acl = $this->get_capability('ACL');
- $namespace = $this->get_namespace();
- $options = array();
-
- // check if the folder is a namespace prefix
- if (!empty($namespace)) {
- $mbox = $folder . $this->delimiter;
- foreach ($namespace as $ns) {
- if (!empty($ns)) {
- foreach ($ns as $item) {
- if ($item[0] === $mbox) {
- $options['is_root'] = true;
- break 2;
- }
- }
- }
- }
- }
- // check if the folder is other user virtual-root
- if (!$options['is_root'] && !empty($namespace) && !empty($namespace['other'])) {
- $parts = explode($this->delimiter, $folder);
- if (count($parts) == 2) {
- $mbox = $parts[0] . $this->delimiter;
- foreach ($namespace['other'] as $item) {
- if ($item[0] === $mbox) {
- $options['is_root'] = true;
- break;
- }
- }
- }
- }
-
- $options['name'] = $folder;
- $options['attributes'] = $this->folder_attributes($folder, true);
- $options['namespace'] = $this->folder_namespace($folder);
- $options['special'] = in_array($folder, $this->default_folders);
-
- // Set 'noselect' flag
- if (is_array($options['attributes'])) {
- foreach ($options['attributes'] as $attrib) {
- $attrib = strtolower($attrib);
- if ($attrib == '\noselect' || $attrib == '\nonexistent') {
- $options['noselect'] = true;
- }
- }
- }
- else {
- $options['noselect'] = true;
- }
-
- // Get folder rights (MYRIGHTS)
- if ($acl && ($rights = $this->my_rights($folder))) {
- $options['rights'] = $rights;
- }
-
- // Set 'norename' flag
- if (!empty($options['rights'])) {
- $options['norename'] = !in_array('x', $options['rights']) && !in_array('d', $options['rights']);
-
- if (!$options['noselect']) {
- $options['noselect'] = !in_array('r', $options['rights']);
- }
- }
- else {
- $options['norename'] = $options['is_root'] || $options['namespace'] != 'personal';
- }
-
- // update caches
- $this->icache['options'] = $options;
- $this->update_cache($cache_key, $options);
-
- return $options;
- }
-
-
- /**
- * Synchronizes messages cache.
- *
- * @param string $folder Folder name
- */
- public function folder_sync($folder)
- {
- if ($mcache = $this->get_mcache_engine()) {
- $mcache->synchronize($folder);
- }
- }
-
-
- /**
- * Get message header names for rcube_imap_generic::fetchHeader(s)
- *
- * @return string Space-separated list of header names
- */
- protected function get_fetch_headers()
- {
- if (!empty($this->options['fetch_headers'])) {
- $headers = explode(' ', $this->options['fetch_headers']);
- $headers = array_map('strtoupper', $headers);
- }
- else {
- $headers = array();
- }
-
- if ($this->messages_caching || $this->options['all_headers']) {
- $headers = array_merge($headers, $this->all_headers);
- }
-
- return implode(' ', array_unique($headers));
- }
-
-
- /* -----------------------------------------
- * ACL and METADATA/ANNOTATEMORE 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
- * @since 0.5-beta
- */
- public function set_acl($folder, $user, $acl)
- {
- if (!$this->get_capability('ACL')) {
- return false;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- $this->clear_cache('mailboxes.folder-info.' . $folder);
-
- return $this->conn->setACL($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
- * @since 0.5-beta
- */
- public function delete_acl($folder, $user)
- {
- if (!$this->get_capability('ACL')) {
- return false;
- }
-
- if (!$this->check_connection()) {
- return false;
- }
-
- return $this->conn->deleteACL($folder, $user);
- }
-
-
- /**
- * Returns the access control list for folder (GETACL)
- *
- * @param string $folder Folder name
- *
- * @return array User-rights array on success, NULL on error
- * @since 0.5-beta
- */
- public function get_acl($folder)
- {
- if (!$this->get_capability('ACL')) {
- return null;
- }
-
- if (!$this->check_connection()) {
- return null;
- }
-
- return $this->conn->getACL($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
- * @since 0.5-beta
- */
- public function list_rights($folder, $user)
- {
- if (!$this->get_capability('ACL')) {
- return null;
- }
-
- if (!$this->check_connection()) {
- return null;
- }
-
- return $this->conn->listRights($folder, $user);
- }
-
-
- /**
- * Returns the set of rights that the current user has to
- * folder (MYRIGHTS)
- *
- * @param string $folder Folder name
- *
- * @return array MYRIGHTS response on success, NULL on error
- * @since 0.5-beta
- */
- public function my_rights($folder)
- {
- if (!$this->get_capability('ACL')) {
- return null;
- }
-
- if (!$this->check_connection()) {
- return null;
- }
-
- return $this->conn->myRights($folder);
- }
-
-
- /**
- * Sets IMAP 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
- * @since 0.5-beta
- */
- public function set_metadata($folder, $entries)
- {
- if (!$this->check_connection()) {
- return false;
- }
-
- $this->clear_cache('mailboxes.metadata.', true);
-
- if ($this->get_capability('METADATA') ||
- (!strlen($folder) && $this->get_capability('METADATA-SERVER'))
- ) {
- return $this->conn->setMetadata($folder, $entries);
- }
- else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
- foreach ((array)$entries as $entry => $value) {
- list($ent, $attr) = $this->md2annotate($entry);
- $entries[$entry] = array($ent, $attr, $value);
- }
- return $this->conn->setAnnotation($folder, $entries);
- }
-
- return false;
- }
-
-
- /**
- * Unsets IMAP 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
- * @since 0.5-beta
- */
- public function delete_metadata($folder, $entries)
- {
- if (!$this->check_connection()) {
- return false;
- }
-
- $this->clear_cache('mailboxes.metadata.', true);
-
- if ($this->get_capability('METADATA') ||
- (!strlen($folder) && $this->get_capability('METADATA-SERVER'))
- ) {
- return $this->conn->deleteMetadata($folder, $entries);
- }
- else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
- foreach ((array)$entries as $idx => $entry) {
- list($ent, $attr) = $this->md2annotate($entry);
- $entries[$idx] = array($ent, $attr, NULL);
- }
- return $this->conn->setAnnotation($folder, $entries);
- }
-
- return false;
- }
-
-
- /**
- * Returns IMAP 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)
- *
- * @return array Metadata entry-value hash array on success, NULL on error
- * @since 0.5-beta
- */
- public function get_metadata($folder, $entries, $options=array())
- {
- $entries = (array)$entries;
-
- // create cache key
- // @TODO: this is the simplest solution, but we do the same with folders list
- // maybe we should store data per-entry and merge on request
- sort($options);
- sort($entries);
- $cache_key = 'mailboxes.metadata.' . $folder;
- $cache_key .= '.' . md5(serialize($options).serialize($entries));
-
- // get cached data
- $cached_data = $this->get_cache($cache_key);
-
- if (is_array($cached_data)) {
- return $cached_data;
- }
-
- if (!$this->check_connection()) {
- return null;
- }
-
- if ($this->get_capability('METADATA') ||
- (!strlen($folder) && $this->get_capability('METADATA-SERVER'))
- ) {
- $res = $this->conn->getMetadata($folder, $entries, $options);
- }
- else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
- $queries = array();
- $res = array();
-
- // Convert entry names
- foreach ($entries as $entry) {
- list($ent, $attr) = $this->md2annotate($entry);
- $queries[$attr][] = $ent;
- }
-
- // @TODO: Honor MAXSIZE and DEPTH options
- foreach ($queries as $attrib => $entry) {
- if ($result = $this->conn->getAnnotation($folder, $entry, $attrib)) {
- $res = array_merge_recursive($res, $result);
- }
- }
- }
-
- if (isset($res)) {
- $this->update_cache($cache_key, $res);
- return $res;
- }
-
- return null;
- }
-
-
- /**
- * Converts the METADATA extension entry name into the correct
- * entry-attrib names for older ANNOTATEMORE version.
- *
- * @param string $entry Entry name
- *
- * @return array Entry-attribute list, NULL if not supported (?)
- */
- protected function md2annotate($entry)
- {
- if (substr($entry, 0, 7) == '/shared') {
- return array(substr($entry, 7), 'value.shared');
- }
- else if (substr($entry, 0, 8) == '/private') {
- return array(substr($entry, 8), 'value.priv');
- }
-
- // @TODO: log error
- return null;
- }
-
-
- /* --------------------------------
- * internal caching methods
- * --------------------------------*/
-
- /**
- * Enable or disable indexes caching
- *
- * @param string $type Cache type (@see rcube::get_cache)
- */
- public function set_caching($type)
- {
- if ($type) {
- $this->caching = $type;
- }
- else {
- if ($this->cache) {
- $this->cache->close();
- }
- $this->cache = null;
- $this->caching = false;
- }
- }
-
- /**
- * Getter for IMAP cache object
- */
- protected function get_cache_engine()
- {
- if ($this->caching && !$this->cache) {
- $rcube = rcube::get_instance();
- $ttl = $rcube->config->get('message_cache_lifetime', '10d');
- $this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl);
- }
-
- return $this->cache;
- }
-
- /**
- * Returns cached value
- *
- * @param string $key Cache key
- *
- * @return mixed
- */
- public function get_cache($key)
- {
- if ($cache = $this->get_cache_engine()) {
- return $cache->get($key);
- }
- }
-
- /**
- * Update cache
- *
- * @param string $key Cache key
- * @param mixed $data Data
- */
- public function update_cache($key, $data)
- {
- if ($cache = $this->get_cache_engine()) {
- $cache->set($key, $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
- */
- public function clear_cache($key = null, $prefix_mode = false)
- {
- if ($cache = $this->get_cache_engine()) {
- $cache->remove($key, $prefix_mode);
- }
- }
-
- /**
- * Delete outdated cache entries
- */
- public function expunge_cache()
- {
- if ($this->mcache) {
- $ttl = rcube::get_instance()->config->get('message_cache_lifetime', '10d');
- $this->mcache->expunge($ttl);
- }
-
- if ($this->cache) {
- $this->cache->expunge();
- }
- }
-
-
- /* --------------------------------
- * message caching methods
- * --------------------------------*/
-
- /**
- * Enable or disable messages caching
- *
- * @param boolean $set Flag
- */
- public function set_messages_caching($set)
- {
- if ($set) {
- $this->messages_caching = true;
- }
- else {
- if ($this->mcache) {
- $this->mcache->close();
- }
- $this->mcache = null;
- $this->messages_caching = false;
- }
- }
-
-
- /**
- * Getter for messages cache object
- */
- protected function get_mcache_engine()
- {
- if ($this->messages_caching && !$this->mcache) {
- $rcube = rcube::get_instance();
- if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) {
- $this->mcache = new rcube_imap_cache(
- $dbh, $this, $userid, $this->options['skip_deleted']);
- }
- }
-
- return $this->mcache;
- }
-
-
- /**
- * Clears the messages cache.
- *
- * @param string $folder Folder name
- * @param array $uids Optional message UIDs to remove from cache
- */
- protected function clear_message_cache($folder = null, $uids = null)
- {
- if ($mcache = $this->get_mcache_engine()) {
- $mcache->clear($folder, $uids);
- }
- }
-
-
- /* --------------------------------
- * protected methods
- * --------------------------------*/
-
- /**
- * Validate the given input and save to local properties
- *
- * @param string $sort_field Sort column
- * @param string $sort_order Sort order
- */
- protected function set_sort_order($sort_field, $sort_order)
- {
- if ($sort_field != null) {
- $this->sort_field = asciiwords($sort_field);
- }
- if ($sort_order != null) {
- $this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
- }
- }
-
-
- /**
- * Sort folders first by default folders and then in alphabethical order
- *
- * @param array $a_folders Folders list
- */
- protected function sort_folder_list($a_folders)
- {
- $a_out = $a_defaults = $folders = array();
-
- $delimiter = $this->get_hierarchy_delimiter();
-
- // find default folders and skip folders starting with '.'
- foreach ($a_folders as $i => $folder) {
- if ($folder[0] == '.') {
- continue;
- }
-
- if (($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) {
- $a_defaults[$p] = $folder;
- }
- else {
- $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP');
- }
- }
-
- // sort folders and place defaults on the top
- asort($folders, SORT_LOCALE_STRING);
- ksort($a_defaults);
- $folders = array_merge($a_defaults, array_keys($folders));
-
- // finally we must rebuild the list to move
- // subfolders of default folders to their place...
- // ...also do this for the rest of folders because
- // asort() is not properly sorting case sensitive names
- while (list($key, $folder) = each($folders)) {
- // set the type of folder name variable (#1485527)
- $a_out[] = (string) $folder;
- unset($folders[$key]);
- $this->rsort($folder, $delimiter, $folders, $a_out);
- }
-
- return $a_out;
- }
-
-
- /**
- * Recursive method for sorting folders
- */
- protected function rsort($folder, $delimiter, &$list, &$out)
- {
- while (list($key, $name) = each($list)) {
- if (strpos($name, $folder.$delimiter) === 0) {
- // set the type of folder name variable (#1485527)
- $out[] = (string) $name;
- unset($list[$key]);
- $this->rsort($name, $delimiter, $list, $out);
- }
- }
- reset($list);
- }
-
-
- /**
- * Find UID of the specified message sequence ID
- *
- * @param int $id Message (sequence) ID
- * @param string $folder Folder name
- *
- * @return int Message UID
- */
- public function id2uid($id, $folder = null)
- {
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
- if ($uid = array_search($id, (array)$this->uid_id_map[$folder])) {
- return $uid;
- }
-
- if (!$this->check_connection()) {
- return null;
- }
-
- $uid = $this->conn->ID2UID($folder, $id);
-
- $this->uid_id_map[$folder][$uid] = $id;
-
- return $uid;
- }
-
-
- /**
- * Subscribe/unsubscribe a list of folders and update local cache
- */
- protected function change_subscription($folders, $mode)
- {
- $updated = false;
-
- if (!empty($folders)) {
- if (!$this->check_connection()) {
- return false;
- }
-
- foreach ((array)$folders as $i => $folder) {
- $folders[$i] = $folder;
-
- if ($mode == 'subscribe') {
- $updated = $this->conn->subscribe($folder);
- }
- else if ($mode == 'unsubscribe') {
- $updated = $this->conn->unsubscribe($folder);
- }
- }
- }
-
- // clear cached folders list(s)
- if ($updated) {
- $this->clear_cache('mailboxes', true);
- }
-
- return $updated;
- }
-
-
- /**
- * Increde/decrese messagecount for a specific folder
- */
- protected function set_messagecount($folder, $mode, $increment)
- {
- if (!is_numeric($increment)) {
- return false;
- }
-
- $mode = strtoupper($mode);
- $a_folder_cache = $this->get_cache('messagecount');
-
- if (!is_array($a_folder_cache[$folder]) || !isset($a_folder_cache[$folder][$mode])) {
- return false;
- }
-
- // add incremental value to messagecount
- $a_folder_cache[$folder][$mode] += $increment;
-
- // there's something wrong, delete from cache
- if ($a_folder_cache[$folder][$mode] < 0) {
- unset($a_folder_cache[$folder][$mode]);
- }
-
- // write back to cache
- $this->update_cache('messagecount', $a_folder_cache);
-
- return true;
- }
-
-
- /**
- * Remove messagecount of a specific folder from cache
- */
- protected function clear_messagecount($folder, $mode=null)
- {
- $a_folder_cache = $this->get_cache('messagecount');
-
- if (is_array($a_folder_cache[$folder])) {
- if ($mode) {
- unset($a_folder_cache[$folder][$mode]);
- }
- else {
- unset($a_folder_cache[$folder]);
- }
- $this->update_cache('messagecount', $a_folder_cache);
- }
- }
-
-
- /**
- * Converts date string/object into IMAP date/time format
- */
- protected function date_format($date)
- {
- if (empty($date)) {
- return null;
- }
-
- if (!is_object($date) || !is_a($date, 'DateTime')) {
- try {
- $timestamp = rcube_utils::strtotime($date);
- $date = new DateTime("@".$timestamp);
- }
- catch (Exception $e) {
- return null;
- }
- }
-
- return $date->format('d-M-Y H:i:s O');
- }
-
-
- /**
- * This is our own debug handler for the IMAP connection
- * @access public
- */
- public function debug_handler(&$imap, $message)
- {
- rcube::write_log('imap', $message);
- }
-
-
- /**
- * Deprecated methods (to be removed)
- */
-
- public function decode_address_list($input, $max = null, $decode = true, $fallback = null)
- {
- return rcube_mime::decode_address_list($input, $max, $decode, $fallback);
- }
-
- public function decode_header($input, $fallback = null)
- {
- return rcube_mime::decode_mime_string((string)$input, $fallback);
- }
-
- public static function decode_mime_string($input, $fallback = null)
- {
- return rcube_mime::decode_mime_string($input, $fallback);
- }
-
- public function mime_decode($input, $encoding = '7bit')
- {
- return rcube_mime::decode($input, $encoding);
- }
-
- public static function explode_header_string($separator, $str, $remove_comments = false)
- {
- return rcube_mime::explode_header_string($separator, $str, $remove_comments);
- }
-
- public function select_mailbox($mailbox)
- {
- // do nothing
- }
-
- public function set_mailbox($folder)
- {
- $this->set_folder($folder);
- }
-
- public function get_mailbox_name()
- {
- return $this->get_folder();
- }
-
- public function list_headers($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
- {
- return $this->list_messages($folder, $page, $sort_field, $sort_order, $slice);
- }
-
- public function get_headers($uid, $folder = null, $force = false)
- {
- return $this->get_message_headers($uid, $folder, $force);
- }
-
- public function mailbox_status($folder = null)
- {
- return $this->folder_status($folder);
- }
-
- public function message_index($folder = '', $sort_field = NULL, $sort_order = NULL)
- {
- return $this->index($folder, $sort_field, $sort_order);
- }
-
- public function message_index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
- {
- return $this->index_direct($folder, $sort_field, $sort_order, $skip_cache);
- }
-
- public function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
- {
- return $this->list_folders_subscribed($root, $name, $filter, $rights, $skip_sort);
- }
-
- public function list_unsubscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
- {
- return $this->list_folders($root, $name, $filter, $rights, $skip_sort);
- }
-
- public function get_mailbox_size($folder)
- {
- return $this->folder_size($folder);
- }
-
- public function create_mailbox($folder, $subscribe=false)
- {
- return $this->create_folder($folder, $subscribe);
- }
-
- public function rename_mailbox($folder, $new_name)
- {
- return $this->rename_folder($folder, $new_name);
- }
-
- function delete_mailbox($folder)
- {
- return $this->delete_folder($folder);
- }
-
- function clear_mailbox($folder = null)
- {
- return $this->clear_folder($folder);
- }
-
- public function mailbox_exists($folder, $subscription=false)
- {
- return $this->folder_exists($folder, $subscription);
- }
-
- public function mailbox_namespace($folder)
- {
- return $this->folder_namespace($folder);
- }
-
- public function mod_mailbox($folder, $mode = 'out')
- {
- return $this->mod_folder($folder, $mode);
- }
-
- public function mailbox_attributes($folder, $force=false)
- {
- return $this->folder_attributes($folder, $force);
- }
-
- public function mailbox_data($folder)
- {
- return $this->folder_data($folder);
- }
-
- public function mailbox_info($folder)
- {
- return $this->folder_info($folder);
- }
-
- public function mailbox_sync($folder)
- {
- return $this->folder_sync($folder);
- }
-
- public function expunge($folder='', $clear_cache=true)
- {
- return $this->expunge_folder($folder, $clear_cache);
- }
-
-}
diff --git a/program/include/rcube_imap_cache.php b/program/include/rcube_imap_cache.php
deleted file mode 100644
index f36ace0eb..000000000
--- a/program/include/rcube_imap_cache.php
+++ /dev/null
@@ -1,1160 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_imap_cache.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: |
- | Caching of IMAP folder contents (messages and index) |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- | Author: Aleksander Machniak <alec@alec.pl> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Interface class for accessing Roundcube messages cache
- *
- * @package Cache
- * @author Thomas Bruederli <roundcube@gmail.com>
- * @author Aleksander Machniak <alec@alec.pl>
- * @version 1.0
- */
-class rcube_imap_cache
-{
- /**
- * Instance of rcube_imap
- *
- * @var rcube_imap
- */
- private $imap;
-
- /**
- * Instance of rcube_db
- *
- * @var rcube_db
- */
- private $db;
-
- /**
- * User ID
- *
- * @var int
- */
- private $userid;
-
- /**
- * Internal (in-memory) cache
- *
- * @var array
- */
- private $icache = array();
-
- private $skip_deleted = false;
-
- /**
- * List of known flags. Thanks to this we can handle flag changes
- * with good performance. Bad thing is we need to know used flags.
- */
- public $flags = array(
- 1 => 'SEEN', // RFC3501
- 2 => 'DELETED', // RFC3501
- 4 => 'ANSWERED', // RFC3501
- 8 => 'FLAGGED', // RFC3501
- 16 => 'DRAFT', // RFC3501
- 32 => 'MDNSENT', // RFC3503
- 64 => 'FORWARDED', // RFC5550
- 128 => 'SUBMITPENDING', // RFC5550
- 256 => 'SUBMITTED', // RFC5550
- 512 => 'JUNK',
- 1024 => 'NONJUNK',
- 2048 => 'LABEL1',
- 4096 => 'LABEL2',
- 8192 => 'LABEL3',
- 16384 => 'LABEL4',
- 32768 => 'LABEL5',
- );
-
-
- /**
- * Object constructor.
- */
- function __construct($db, $imap, $userid, $skip_deleted)
- {
- $this->db = $db;
- $this->imap = $imap;
- $this->userid = $userid;
- $this->skip_deleted = $skip_deleted;
- }
-
-
- /**
- * Cleanup actions (on shutdown).
- */
- public function close()
- {
- $this->save_icache();
- $this->icache = null;
- }
-
-
- /**
- * Return (sorted) messages index (UIDs).
- * If index doesn't exist or is invalid, will be updated.
- *
- * @param string $mailbox Folder name
- * @param string $sort_field Sorting column
- * @param string $sort_order Sorting order (ASC|DESC)
- * @param bool $exiting Skip index initialization if it doesn't exist in DB
- *
- * @return array Messages index
- */
- function get_index($mailbox, $sort_field = null, $sort_order = null, $existing = false)
- {
- if (empty($this->icache[$mailbox])) {
- $this->icache[$mailbox] = array();
- }
-
- $sort_order = strtoupper($sort_order) == 'ASC' ? 'ASC' : 'DESC';
-
- // Seek in internal cache
- if (array_key_exists('index', $this->icache[$mailbox])) {
- // The index was fetched from database already, but not validated yet
- if (!array_key_exists('object', $this->icache[$mailbox]['index'])) {
- $index = $this->icache[$mailbox]['index'];
- }
- // We've got a valid index
- else if ($sort_field == 'ANY' || $this->icache[$mailbox]['index']['sort_field'] == $sort_field) {
- $result = $this->icache[$mailbox]['index']['object'];
- if ($result->get_parameters('ORDER') != $sort_order) {
- $result->revert();
- }
- return $result;
- }
- }
-
- // Get index from DB (if DB wasn't already queried)
- if (empty($index) && empty($this->icache[$mailbox]['index_queried'])) {
- $index = $this->get_index_row($mailbox);
-
- // set the flag that DB was already queried for index
- // this way we'll be able to skip one SELECT, when
- // get_index() is called more than once
- $this->icache[$mailbox]['index_queried'] = true;
- }
-
- $data = null;
-
- // @TODO: Think about skipping validation checks.
- // If we could check only every 10 minutes, we would be able to skip
- // expensive checks, mailbox selection or even IMAP connection, this would require
- // additional logic to force cache invalidation in some cases
- // and many rcube_imap changes to connect when needed
-
- // Entry exists, check cache status
- if (!empty($index)) {
- $exists = true;
-
- if ($sort_field == 'ANY') {
- $sort_field = $index['sort_field'];
- }
-
- if ($sort_field != $index['sort_field']) {
- $is_valid = false;
- }
- else {
- $is_valid = $this->validate($mailbox, $index, $exists);
- }
-
- if ($is_valid) {
- $data = $index['object'];
- // revert the order if needed
- if ($data->get_parameters('ORDER') != $sort_order) {
- $data->revert();
- }
- }
- }
- else {
- if ($existing) {
- return null;
- }
- else if ($sort_field == 'ANY') {
- $sort_field = '';
- }
-
- // Got it in internal cache, so the row already exist
- $exists = array_key_exists('index', $this->icache[$mailbox]);
- }
-
- // Index not found, not valid or sort field changed, get index from IMAP server
- if ($data === null) {
- // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
- $mbox_data = $this->imap->folder_data($mailbox);
- $data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data);
-
- // insert/update
- $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists, $index['modseq']);
- }
-
- $this->icache[$mailbox]['index'] = array(
- 'object' => $data,
- 'sort_field' => $sort_field,
- 'modseq' => !empty($index['modseq']) ? $index['modseq'] : $mbox_data['HIGHESTMODSEQ']
- );
-
- return $data;
- }
-
-
- /**
- * Return messages thread.
- * If threaded index doesn't exist or is invalid, will be updated.
- *
- * @param string $mailbox Folder name
- * @param string $sort_field Sorting column
- * @param string $sort_order Sorting order (ASC|DESC)
- *
- * @return array Messages threaded index
- */
- function get_thread($mailbox)
- {
- if (empty($this->icache[$mailbox])) {
- $this->icache[$mailbox] = array();
- }
-
- // Seek in internal cache
- if (array_key_exists('thread', $this->icache[$mailbox])) {
- return $this->icache[$mailbox]['thread']['object'];
- }
-
- // Get thread from DB (if DB wasn't already queried)
- if (empty($this->icache[$mailbox]['thread_queried'])) {
- $index = $this->get_thread_row($mailbox);
-
- // set the flag that DB was already queried for thread
- // this way we'll be able to skip one SELECT, when
- // get_thread() is called more than once or after clear()
- $this->icache[$mailbox]['thread_queried'] = true;
- }
-
- // Entry exist, check cache status
- if (!empty($index)) {
- $exists = true;
- $is_valid = $this->validate($mailbox, $index, $exists);
-
- if (!$is_valid) {
- $index = null;
- }
- }
-
- // Index not found or not valid, get index from IMAP server
- if ($index === null) {
- // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
- $mbox_data = $this->imap->folder_data($mailbox);
-
- if ($mbox_data['EXISTS']) {
- // get all threads (default sort order)
- $threads = $this->imap->fetch_threads($mailbox, true);
- }
- else {
- $threads = new rcube_result_thread($mailbox, '* THREAD');
- }
-
- $index['object'] = $threads;
-
- // insert/update
- $this->add_thread_row($mailbox, $threads, $mbox_data, $exists);
- }
-
- $this->icache[$mailbox]['thread'] = $index;
-
- return $index['object'];
- }
-
-
- /**
- * Returns list of messages (headers). See rcube_imap::fetch_headers().
- *
- * @param string $mailbox Folder name
- * @param array $msgs Message UIDs
- *
- * @return array The list of messages (rcube_message_header) indexed by UID
- */
- function get_messages($mailbox, $msgs = array())
- {
- if (empty($msgs)) {
- return array();
- }
-
- // Fetch messages from cache
- $sql_result = $this->db->query(
- "SELECT uid, data, flags"
- ." FROM ".$this->db->table_name('cache_messages')
- ." WHERE user_id = ?"
- ." AND mailbox = ?"
- ." AND uid IN (".$this->db->array2list($msgs, 'integer').")",
- $this->userid, $mailbox);
-
- $msgs = array_flip($msgs);
- $result = array();
-
- while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $uid = intval($sql_arr['uid']);
- $result[$uid] = $this->build_message($sql_arr);
-
- if (!empty($result[$uid])) {
- // save memory, we don't need message body here (?)
- $result[$uid]->body = null;
-
- unset($msgs[$uid]);
- }
- }
-
- // Fetch not found messages from IMAP server
- if (!empty($msgs)) {
- $messages = $this->imap->fetch_headers($mailbox, array_keys($msgs), false, true);
-
- // Insert to DB and add to result list
- if (!empty($messages)) {
- foreach ($messages as $msg) {
- $this->add_message($mailbox, $msg, !array_key_exists($msg->uid, $result));
- $result[$msg->uid] = $msg;
- }
- }
- }
-
- return $result;
- }
-
-
- /**
- * Returns message data.
- *
- * @param string $mailbox Folder name
- * @param int $uid Message UID
- * @param bool $update If message doesn't exists in cache it will be fetched
- * from IMAP server
- * @param bool $no_cache Enables internal cache usage
- *
- * @return rcube_message_header Message data
- */
- function get_message($mailbox, $uid, $update = true, $cache = true)
- {
- // Check internal cache
- if ($this->icache['__message']
- && $this->icache['__message']['mailbox'] == $mailbox
- && $this->icache['__message']['object']->uid == $uid
- ) {
- return $this->icache['__message']['object'];
- }
-
- $sql_result = $this->db->query(
- "SELECT flags, data"
- ." FROM ".$this->db->table_name('cache_messages')
- ." WHERE user_id = ?"
- ." AND mailbox = ?"
- ." AND uid = ?",
- $this->userid, $mailbox, (int)$uid);
-
- if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $message = $this->build_message($sql_arr);
- $found = true;
- }
-
- // Get the message from IMAP server
- if (empty($message) && $update) {
- $message = $this->imap->get_message_headers($uid, $mailbox, true);
- // cache will be updated in close(), see below
- }
-
- // Save the message in internal cache, will be written to DB in close()
- // Common scenario: user opens unseen message
- // - get message (SELECT)
- // - set message headers/structure (INSERT or UPDATE)
- // - set \Seen flag (UPDATE)
- // This way we can skip one UPDATE
- if (!empty($message) && $cache) {
- // Save current message from internal cache
- $this->save_icache();
-
- $this->icache['__message'] = array(
- 'object' => $message,
- 'mailbox' => $mailbox,
- 'exists' => $found,
- 'md5sum' => md5(serialize($message)),
- );
- }
-
- return $message;
- }
-
-
- /**
- * Saves the message in cache.
- *
- * @param string $mailbox Folder name
- * @param rcube_message_header $message Message data
- * @param bool $force Skips message in-cache existance check
- */
- function add_message($mailbox, $message, $force = false)
- {
- if (!is_object($message) || empty($message->uid)) {
- return;
- }
-
- $msg = serialize($this->db->encode(clone $message));
- $flags = 0;
-
- if (!empty($message->flags)) {
- foreach ($this->flags as $idx => $flag) {
- if (!empty($message->flags[$flag])) {
- $flags += $idx;
- }
- }
- }
- unset($msg->flags);
-
- // update cache record (even if it exists, the update
- // here will work as select, assume row exist if affected_rows=0)
- if (!$force) {
- $res = $this->db->query(
- "UPDATE ".$this->db->table_name('cache_messages')
- ." SET flags = ?, data = ?, changed = ".$this->db->now()
- ." WHERE user_id = ?"
- ." AND mailbox = ?"
- ." AND uid = ?",
- $flags, $msg, $this->userid, $mailbox, (int) $message->uid);
-
- if ($this->db->affected_rows()) {
- return;
- }
- }
-
- // insert new record
- $this->db->query(
- "INSERT INTO ".$this->db->table_name('cache_messages')
- ." (user_id, mailbox, uid, flags, changed, data)"
- ." VALUES (?, ?, ?, ?, ".$this->db->now().", ?)",
- $this->userid, $mailbox, (int) $message->uid, $flags, $msg);
- }
-
-
- /**
- * Sets the flag for specified message.
- *
- * @param string $mailbox Folder name
- * @param array $uids Message UIDs or null to change flag
- * of all messages in a folder
- * @param string $flag The name of the flag
- * @param bool $enabled Flag state
- */
- function change_flag($mailbox, $uids, $flag, $enabled = false)
- {
- if (empty($uids)) {
- return;
- }
-
- $flag = strtoupper($flag);
- $idx = (int) array_search($flag, $this->flags);
- $uids = (array) $uids;
-
- if (!$idx) {
- return;
- }
-
- // Internal cache update
- if (($message = $this->icache['__message'])
- && $message['mailbox'] === $mailbox
- && in_array($message['object']->uid, $uids)
- ) {
- $message['object']->flags[$flag] = $enabled;
-
- if (count($uids) == 1) {
- return;
- }
- }
-
- $this->db->query(
- "UPDATE ".$this->db->table_name('cache_messages')
- ." SET changed = ".$this->db->now()
- .", flags = flags ".($enabled ? "+ $idx" : "- $idx")
- ." WHERE user_id = ?"
- ." AND mailbox = ?"
- .($uids !== null ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "")
- ." AND (flags & $idx) ".($enabled ? "= 0" : "= $idx"),
- $this->userid, $mailbox);
- }
-
-
- /**
- * Removes message(s) from cache.
- *
- * @param string $mailbox Folder name
- * @param array $uids Message UIDs, NULL removes all messages
- */
- function remove_message($mailbox = null, $uids = null)
- {
- if (!strlen($mailbox)) {
- $this->db->query(
- "DELETE FROM ".$this->db->table_name('cache_messages')
- ." WHERE user_id = ?",
- $this->userid);
- }
- else {
- // Remove the message from internal cache
- if (!empty($uids) && ($message = $this->icache['__message'])
- && $message['mailbox'] === $mailbox
- && in_array($message['object']->uid, (array)$uids)
- ) {
- $this->icache['__message'] = null;
- }
-
- $this->db->query(
- "DELETE FROM ".$this->db->table_name('cache_messages')
- ." WHERE user_id = ?"
- ." AND mailbox = ?"
- .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : ""),
- $this->userid, $mailbox);
- }
- }
-
-
- /**
- * Clears index cache.
- *
- * @param string $mailbox Folder name
- * @param bool $remove Enable to remove the DB row
- */
- function remove_index($mailbox = null, $remove = false)
- {
- // The index should be only removed from database when
- // UIDVALIDITY was detected or the mailbox is empty
- // otherwise use 'valid' flag to not loose HIGHESTMODSEQ value
- if ($remove) {
- $this->db->query(
- "DELETE FROM ".$this->db->table_name('cache_index')
- ." WHERE user_id = ?"
- .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
- $this->userid
- );
- }
- else {
- $this->db->query(
- "UPDATE ".$this->db->table_name('cache_index')
- ." SET valid = 0"
- ." WHERE user_id = ?"
- .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
- $this->userid
- );
- }
-
- if (strlen($mailbox)) {
- unset($this->icache[$mailbox]['index']);
- // Index removed, set flag to skip SELECT query in get_index()
- $this->icache[$mailbox]['index_queried'] = true;
- }
- else {
- $this->icache = array();
- }
- }
-
-
- /**
- * Clears thread cache.
- *
- * @param string $mailbox Folder name
- */
- function remove_thread($mailbox = null)
- {
- $this->db->query(
- "DELETE FROM ".$this->db->table_name('cache_thread')
- ." WHERE user_id = ?"
- .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
- $this->userid
- );
-
- if (strlen($mailbox)) {
- unset($this->icache[$mailbox]['thread']);
- // Thread data removed, set flag to skip SELECT query in get_thread()
- $this->icache[$mailbox]['thread_queried'] = true;
- }
- else {
- $this->icache = array();
- }
- }
-
-
- /**
- * Clears the cache.
- *
- * @param string $mailbox Folder name
- * @param array $uids Message UIDs, NULL removes all messages in a folder
- */
- function clear($mailbox = null, $uids = null)
- {
- $this->remove_index($mailbox, true);
- $this->remove_thread($mailbox);
- $this->remove_message($mailbox, $uids);
- }
-
-
- /**
- * Delete cache entries older than TTL
- *
- * @param string $ttl Lifetime of message cache entries
- */
- function expunge($ttl)
- {
- // get expiration timestamp
- $ts = get_offset_time($ttl, -1);
-
- $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages')
- ." WHERE changed < " . $this->db->fromunixtime($ts));
-
- $this->db->query("DELETE FROM ".$this->db->table_name('cache_index')
- ." WHERE changed < " . $this->db->fromunixtime($ts));
-
- $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread')
- ." WHERE changed < " . $this->db->fromunixtime($ts));
- }
-
-
- /**
- * Fetches index data from database
- */
- private function get_index_row($mailbox)
- {
- // Get index from DB
- $sql_result = $this->db->query(
- "SELECT data, valid"
- ." FROM ".$this->db->table_name('cache_index')
- ." WHERE user_id = ?"
- ." AND mailbox = ?",
- $this->userid, $mailbox);
-
- if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $data = explode('@', $sql_arr['data']);
- $index = @unserialize($data[0]);
- unset($data[0]);
-
- if (empty($index)) {
- $index = new rcube_result_index($mailbox);
- }
-
- return array(
- 'valid' => $sql_arr['valid'],
- 'object' => $index,
- 'sort_field' => $data[1],
- 'deleted' => $data[2],
- 'validity' => $data[3],
- 'uidnext' => $data[4],
- 'modseq' => $data[5],
- );
- }
-
- return null;
- }
-
-
- /**
- * Fetches thread data from database
- */
- private function get_thread_row($mailbox)
- {
- // Get thread from DB
- $sql_result = $this->db->query(
- "SELECT data"
- ." FROM ".$this->db->table_name('cache_thread')
- ." WHERE user_id = ?"
- ." AND mailbox = ?",
- $this->userid, $mailbox);
-
- if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $data = explode('@', $sql_arr['data']);
- $thread = @unserialize($data[0]);
- unset($data[0]);
-
- if (empty($thread)) {
- $thread = new rcube_result_thread($mailbox);
- }
-
- return array(
- 'object' => $thread,
- 'deleted' => $data[1],
- 'validity' => $data[2],
- 'uidnext' => $data[3],
- );
- }
-
- return null;
- }
-
-
- /**
- * Saves index data into database
- */
- private function add_index_row($mailbox, $sort_field,
- $data, $mbox_data = array(), $exists = false, $modseq = null)
- {
- $data = array(
- serialize($data),
- $sort_field,
- (int) $this->skip_deleted,
- (int) $mbox_data['UIDVALIDITY'],
- (int) $mbox_data['UIDNEXT'],
- $modseq ? $modseq : $mbox_data['HIGHESTMODSEQ'],
- );
- $data = implode('@', $data);
-
- if ($exists) {
- $sql_result = $this->db->query(
- "UPDATE ".$this->db->table_name('cache_index')
- ." SET data = ?, valid = 1, changed = ".$this->db->now()
- ." WHERE user_id = ?"
- ." AND mailbox = ?",
- $data, $this->userid, $mailbox);
- }
- else {
- $sql_result = $this->db->query(
- "INSERT INTO ".$this->db->table_name('cache_index')
- ." (user_id, mailbox, data, valid, changed)"
- ." VALUES (?, ?, ?, 1, ".$this->db->now().")",
- $this->userid, $mailbox, $data);
- }
- }
-
-
- /**
- * Saves thread data into database
- */
- private function add_thread_row($mailbox, $data, $mbox_data = array(), $exists = false)
- {
- $data = array(
- serialize($data),
- (int) $this->skip_deleted,
- (int) $mbox_data['UIDVALIDITY'],
- (int) $mbox_data['UIDNEXT'],
- );
- $data = implode('@', $data);
-
- if ($exists) {
- $sql_result = $this->db->query(
- "UPDATE ".$this->db->table_name('cache_thread')
- ." SET data = ?, changed = ".$this->db->now()
- ." WHERE user_id = ?"
- ." AND mailbox = ?",
- $data, $this->userid, $mailbox);
- }
- else {
- $sql_result = $this->db->query(
- "INSERT INTO ".$this->db->table_name('cache_thread')
- ." (user_id, mailbox, data, changed)"
- ." VALUES (?, ?, ?, ".$this->db->now().")",
- $this->userid, $mailbox, $data);
- }
- }
-
-
- /**
- * Checks index/thread validity
- */
- private function validate($mailbox, $index, &$exists = true)
- {
- $object = $index['object'];
- $is_thread = is_a($object, 'rcube_result_thread');
-
- // sanity check
- if (empty($object)) {
- return false;
- }
-
- // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
- $mbox_data = $this->imap->folder_data($mailbox);
-
- // @TODO: Think about skipping validation checks.
- // If we could check only every 10 minutes, we would be able to skip
- // expensive checks, mailbox selection or even IMAP connection, this would require
- // additional logic to force cache invalidation in some cases
- // and many rcube_imap changes to connect when needed
-
- // Check UIDVALIDITY
- if ($index['validity'] != $mbox_data['UIDVALIDITY']) {
- $this->clear($mailbox);
- $exists = false;
- return false;
- }
-
- // Folder is empty but cache isn't
- if (empty($mbox_data['EXISTS'])) {
- if (!$object->is_empty()) {
- $this->clear($mailbox);
- $exists = false;
- return false;
- }
- }
- // Folder is not empty but cache is
- else if ($object->is_empty()) {
- unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']);
- return false;
- }
-
- // Validation flag
- if (!$is_thread && empty($index['valid'])) {
- unset($this->icache[$mailbox]['index']);
- return false;
- }
-
- // Index was created with different skip_deleted setting
- if ($this->skip_deleted != $index['deleted']) {
- return false;
- }
-
- // Check HIGHESTMODSEQ
- if (!empty($index['modseq']) && !empty($mbox_data['HIGHESTMODSEQ'])
- && $index['modseq'] == $mbox_data['HIGHESTMODSEQ']
- ) {
- return true;
- }
-
- // Check UIDNEXT
- if ($index['uidnext'] != $mbox_data['UIDNEXT']) {
- unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']);
- return false;
- }
-
- // @TODO: find better validity check for threaded index
- if ($is_thread) {
- // check messages number...
- if (!$this->skip_deleted && $mbox_data['EXISTS'] != $object->count_messages()) {
- return false;
- }
- return true;
- }
-
- // The rest of checks, more expensive
- if (!empty($this->skip_deleted)) {
- // compare counts if available
- if (!empty($mbox_data['UNDELETED'])
- && $mbox_data['UNDELETED']->count() != $object->count()
- ) {
- return false;
- }
- // compare UID sets
- if (!empty($mbox_data['UNDELETED'])) {
- $uids_new = $mbox_data['UNDELETED']->get();
- $uids_old = $object->get();
-
- if (count($uids_new) != count($uids_old)) {
- return false;
- }
-
- sort($uids_new, SORT_NUMERIC);
- sort($uids_old, SORT_NUMERIC);
-
- if ($uids_old != $uids_new)
- return false;
- }
- else {
- // get all undeleted messages excluding cached UIDs
- $ids = $this->imap->search_once($mailbox, 'ALL UNDELETED NOT UID '.
- rcube_imap_generic::compressMessageSet($object->get()));
-
- if (!$ids->is_empty()) {
- return false;
- }
- }
- }
- else {
- // check messages number...
- if ($mbox_data['EXISTS'] != $object->count()) {
- return false;
- }
- // ... and max UID
- if ($object->max() != $this->imap->id2uid($mbox_data['EXISTS'], $mailbox, true)) {
- return false;
- }
- }
-
- return true;
- }
-
-
- /**
- * Synchronizes the mailbox.
- *
- * @param string $mailbox Folder name
- */
- function synchronize($mailbox)
- {
- // RFC4549: Synchronization Operations for Disconnected IMAP4 Clients
- // RFC4551: IMAP Extension for Conditional STORE Operation
- // or Quick Flag Changes Resynchronization
- // RFC5162: IMAP Extensions for Quick Mailbox Resynchronization
-
- // @TODO: synchronize with other methods?
- $qresync = $this->imap->get_capability('QRESYNC');
- $condstore = $qresync ? true : $this->imap->get_capability('CONDSTORE');
-
- if (!$qresync && !$condstore) {
- return;
- }
-
- // Get stored index
- $index = $this->get_index_row($mailbox);
-
- // database is empty
- if (empty($index)) {
- // set the flag that DB was already queried for index
- // this way we'll be able to skip one SELECT in get_index()
- $this->icache[$mailbox]['index_queried'] = true;
- return;
- }
-
- $this->icache[$mailbox]['index'] = $index;
-
- // no last HIGHESTMODSEQ value
- if (empty($index['modseq'])) {
- return;
- }
-
- if (!$this->imap->check_connection()) {
- return;
- }
-
- // Enable QRESYNC
- $res = $this->imap->conn->enable($qresync ? 'QRESYNC' : 'CONDSTORE');
- if ($res === false) {
- return;
- }
-
- // Close mailbox if already selected to get most recent data
- if ($this->imap->conn->selected == $mailbox) {
- $this->imap->conn->close();
- }
-
- // Get mailbox data (UIDVALIDITY, HIGHESTMODSEQ, counters, etc.)
- $mbox_data = $this->imap->folder_data($mailbox);
-
- if (empty($mbox_data)) {
- return;
- }
-
- // Check UIDVALIDITY
- if ($index['validity'] != $mbox_data['UIDVALIDITY']) {
- $this->clear($mailbox);
- return;
- }
-
- // QRESYNC not supported on specified mailbox
- if (!empty($mbox_data['NOMODSEQ']) || empty($mbox_data['HIGHESTMODSEQ'])) {
- return;
- }
-
- // Nothing new
- if ($mbox_data['HIGHESTMODSEQ'] == $index['modseq']) {
- return;
- }
-
- $uids = array();
- $removed = array();
-
- // Get known UIDs
- $sql_result = $this->db->query(
- "SELECT uid"
- ." FROM ".$this->db->table_name('cache_messages')
- ." WHERE user_id = ?"
- ." AND mailbox = ?",
- $this->userid, $mailbox);
-
- while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $uids[] = $sql_arr['uid'];
- }
-
- // Synchronize messages data
- if (!empty($uids)) {
- // Get modified flags and vanished messages
- // UID FETCH 1:* (FLAGS) (CHANGEDSINCE 0123456789 VANISHED)
- $result = $this->imap->conn->fetch($mailbox,
- $uids, true, array('FLAGS'), $index['modseq'], $qresync);
-
- if (!empty($result)) {
- foreach ($result as $id => $msg) {
- $uid = $msg->uid;
- // Remove deleted message
- if ($this->skip_deleted && !empty($msg->flags['DELETED'])) {
- $removed[] = $uid;
- // Invalidate index
- $index['valid'] = false;
- continue;
- }
-
- $flags = 0;
- if (!empty($msg->flags)) {
- foreach ($this->flags as $idx => $flag) {
- if (!empty($msg->flags[$flag])) {
- $flags += $idx;
- }
- }
- }
-
- $this->db->query(
- "UPDATE ".$this->db->table_name('cache_messages')
- ." SET flags = ?, changed = ".$this->db->now()
- ." WHERE user_id = ?"
- ." AND mailbox = ?"
- ." AND uid = ?"
- ." AND flags <> ?",
- $flags, $this->userid, $mailbox, $uid, $flags);
- }
- }
-
- // VANISHED found?
- if ($qresync) {
- $mbox_data = $this->imap->folder_data($mailbox);
-
- // Removed messages found
- $uids = rcube_imap_generic::uncompressMessageSet($mbox_data['VANISHED']);
- if (!empty($uids)) {
- $removed = array_merge($removed, $uids);
- // Invalidate index
- $index['valid'] = false;
- }
- }
-
- // remove messages from database
- if (!empty($removed)) {
- $this->remove_message($mailbox, $removed);
- }
- }
-
- // Invalidate thread index (?)
- if (!$index['valid']) {
- $this->remove_thread($mailbox);
- }
-
- $sort_field = $index['sort_field'];
- $sort_order = $index['object']->get_parameters('ORDER');
- $exists = true;
-
- // Validate index
- if (!$this->validate($mailbox, $index, $exists)) {
- // Update index
- $data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data);
- }
- else {
- $data = $index['object'];
- }
-
- // update index and/or HIGHESTMODSEQ value
- $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists);
-
- // update internal cache for get_index()
- $this->icache[$mailbox]['index']['object'] = $data;
- }
-
-
- /**
- * Converts cache row into message object.
- *
- * @param array $sql_arr Message row data
- *
- * @return rcube_message_header Message object
- */
- private function build_message($sql_arr)
- {
- $message = $this->db->decode(unserialize($sql_arr['data']));
-
- if ($message) {
- $message->flags = array();
- foreach ($this->flags as $idx => $flag) {
- if (($sql_arr['flags'] & $idx) == $idx) {
- $message->flags[$flag] = true;
- }
- }
- }
-
- return $message;
- }
-
-
- /**
- * Saves message stored in internal cache
- */
- private function save_icache()
- {
- // Save current message from internal cache
- if ($message = $this->icache['__message']) {
- // clean up some object's data
- $object = $this->message_object_prepare($message['object']);
-
- // calculate current md5 sum
- $md5sum = md5(serialize($object));
-
- if ($message['md5sum'] != $md5sum) {
- $this->add_message($message['mailbox'], $object, !$message['exists']);
- }
-
- $this->icache['__message']['md5sum'] = $md5sum;
- }
- }
-
-
- /**
- * Prepares message object to be stored in database.
- */
- private function message_object_prepare($msg)
- {
- // Remove body too big (>25kB)
- if ($msg->body && strlen($msg->body) > 25 * 1024) {
- unset($msg->body);
- }
-
- // Fix mimetype which might be broken by some code when message is displayed
- // Another solution would be to use object's copy in rcube_message class
- // to prevent related issues, however I'm not sure which is better
- if ($msg->mimetype) {
- list($msg->ctype_primary, $msg->ctype_secondary) = explode('/', $msg->mimetype);
- }
-
- if (is_array($msg->structure->parts)) {
- foreach ($msg->structure->parts as $idx => $part) {
- $msg->structure->parts[$idx] = $this->message_object_prepare($part);
- }
- }
-
- return $msg;
- }
-
-
- /**
- * Fetches index data from IMAP server
- */
- private function get_index_data($mailbox, $sort_field, $sort_order, $mbox_data = array())
- {
- if (empty($mbox_data)) {
- $mbox_data = $this->imap->folder_data($mailbox);
- }
-
- if ($mbox_data['EXISTS']) {
- // fetch sorted sequence numbers
- $index = $this->imap->index_direct($mailbox, $sort_field, $sort_order);
- }
- else {
- $index = new rcube_result_index($mailbox, '* SORT');
- }
-
- return $index;
- }
-}
-
-// for backward compat.
-class rcube_mail_header extends rcube_message_header { }
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
deleted file mode 100644
index 52bf0e37a..000000000
--- a/program/include/rcube_imap_generic.php
+++ /dev/null
@@ -1,3701 +0,0 @@
-<?php
-
-/**
- +-----------------------------------------------------------------------+
- | program/include/rcube_imap_generic.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | |
- | 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 alternative IMAP library that doesn't rely on the standard |
- | C-Client based version. This allows to function regardless |
- | of whether or not the PHP build it's running on has IMAP |
- | functionality built-in. |
- | |
- | Based on Iloha IMAP Library. See http://ilohamail.org/ for details |
- | |
- +-----------------------------------------------------------------------+
- | Author: Aleksander Machniak <alec@alec.pl> |
- | Author: Ryo Chijiiwa <Ryo@IlohaMail.org> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * PHP based wrapper class to connect to an IMAP server
- *
- * @package Mail
- * @author Aleksander Machniak <alec@alec.pl>
- */
-class rcube_imap_generic
-{
- public $error;
- public $errornum;
- public $result;
- public $resultcode;
- public $selected;
- public $data = array();
- public $flags = array(
- 'SEEN' => '\\Seen',
- 'DELETED' => '\\Deleted',
- 'ANSWERED' => '\\Answered',
- 'DRAFT' => '\\Draft',
- 'FLAGGED' => '\\Flagged',
- 'FORWARDED' => '$Forwarded',
- 'MDNSENT' => '$MDNSent',
- '*' => '\\*',
- );
-
- private $fp;
- private $host;
- private $logged = false;
- private $capability = array();
- private $capability_readed = false;
- private $prefs;
- private $cmd_tag;
- private $cmd_num = 0;
- private $resourceid;
- private $_debug = false;
- private $_debug_handler = false;
-
- const ERROR_OK = 0;
- const ERROR_NO = -1;
- const ERROR_BAD = -2;
- const ERROR_BYE = -3;
- const ERROR_UNKNOWN = -4;
- const ERROR_COMMAND = -5;
- const ERROR_READONLY = -6;
-
- const COMMAND_NORESPONSE = 1;
- const COMMAND_CAPABILITY = 2;
- const COMMAND_LASTLINE = 4;
-
- /**
- * Object constructor
- */
- function __construct()
- {
- }
-
- /**
- * Send simple (one line) command to the connection stream
- *
- * @param string $string Command string
- * @param bool $endln True if CRLF need to be added at the end of command
- *
- * @param int Number of bytes sent, False on error
- */
- function putLine($string, $endln=true)
- {
- if (!$this->fp)
- return false;
-
- if ($this->_debug) {
- $this->debug('C: '. rtrim($string));
- }
-
- $res = fwrite($this->fp, $string . ($endln ? "\r\n" : ''));
-
- if ($res === false) {
- @fclose($this->fp);
- $this->fp = null;
- }
-
- return $res;
- }
-
- /**
- * Send command to the connection stream with Command Continuation
- * Requests (RFC3501 7.5) and LITERAL+ (RFC2088) support
- *
- * @param string $string Command string
- * @param bool $endln True if CRLF need to be added at the end of command
- *
- * @return int|bool Number of bytes sent, False on error
- */
- function putLineC($string, $endln=true)
- {
- if (!$this->fp) {
- return false;
- }
-
- if ($endln) {
- $string .= "\r\n";
- }
-
- $res = 0;
- if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) {
- for ($i=0, $cnt=count($parts); $i<$cnt; $i++) {
- if (preg_match('/^\{([0-9]+)\}\r\n$/', $parts[$i+1], $matches)) {
- // LITERAL+ support
- if ($this->prefs['literal+']) {
- $parts[$i+1] = sprintf("{%d+}\r\n", $matches[1]);
- }
-
- $bytes = $this->putLine($parts[$i].$parts[$i+1], false);
- if ($bytes === false)
- return false;
- $res += $bytes;
-
- // don't wait if server supports LITERAL+ capability
- if (!$this->prefs['literal+']) {
- $line = $this->readLine(1000);
- // handle error in command
- if ($line[0] != '+')
- return false;
- }
- $i++;
- }
- else {
- $bytes = $this->putLine($parts[$i], false);
- if ($bytes === false)
- return false;
- $res += $bytes;
- }
- }
- }
- return $res;
- }
-
- /**
- * Reads line from the connection stream
- *
- * @param int $size Buffer size
- *
- * @return string Line of text response
- */
- function readLine($size=1024)
- {
- $line = '';
-
- if (!$size) {
- $size = 1024;
- }
-
- do {
- if ($this->eof()) {
- return $line ? $line : NULL;
- }
-
- $buffer = fgets($this->fp, $size);
-
- if ($buffer === false) {
- $this->closeSocket();
- break;
- }
- if ($this->_debug) {
- $this->debug('S: '. rtrim($buffer));
- }
- $line .= $buffer;
- } while (substr($buffer, -1) != "\n");
-
- return $line;
- }
-
- /**
- * Reads more data from the connection stream when provided
- * data contain string literal
- *
- * @param string $line Response text
- * @param bool $escape Enables escaping
- *
- * @return string Line of text response
- */
- function multLine($line, $escape = false)
- {
- $line = rtrim($line);
- if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
- $out = '';
- $str = substr($line, 0, -strlen($m[0]));
- $bytes = $m[1];
-
- while (strlen($out) < $bytes) {
- $line = $this->readBytes($bytes);
- if ($line === NULL)
- break;
- $out .= $line;
- }
-
- $line = $str . ($escape ? $this->escape($out) : $out);
- }
-
- return $line;
- }
-
- /**
- * Reads specified number of bytes from the connection stream
- *
- * @param int $bytes Number of bytes to get
- *
- * @return string Response text
- */
- function readBytes($bytes)
- {
- $data = '';
- $len = 0;
- while ($len < $bytes && !$this->eof())
- {
- $d = fread($this->fp, $bytes-$len);
- if ($this->_debug) {
- $this->debug('S: '. $d);
- }
- $data .= $d;
- $data_len = strlen($data);
- if ($len == $data_len) {
- break; // nothing was read -> exit to avoid apache lockups
- }
- $len = $data_len;
- }
-
- return $data;
- }
-
- /**
- * Reads complete response to the IMAP command
- *
- * @param array $untagged Will be filled with untagged response lines
- *
- * @return string Response text
- */
- function readReply(&$untagged=null)
- {
- do {
- $line = trim($this->readLine(1024));
- // store untagged response lines
- if ($line[0] == '*')
- $untagged[] = $line;
- } while ($line[0] == '*');
-
- if ($untagged)
- $untagged = join("\n", $untagged);
-
- return $line;
- }
-
- /**
- * Response parser.
- *
- * @param string $string Response text
- * @param string $err_prefix Error message prefix
- *
- * @return int Response status
- */
- function parseResult($string, $err_prefix='')
- {
- if (preg_match('/^[a-z0-9*]+ (OK|NO|BAD|BYE)(.*)$/i', trim($string), $matches)) {
- $res = strtoupper($matches[1]);
- $str = trim($matches[2]);
-
- if ($res == 'OK') {
- $this->errornum = self::ERROR_OK;
- } else if ($res == 'NO') {
- $this->errornum = self::ERROR_NO;
- } else if ($res == 'BAD') {
- $this->errornum = self::ERROR_BAD;
- } else if ($res == 'BYE') {
- $this->closeSocket();
- $this->errornum = self::ERROR_BYE;
- }
-
- if ($str) {
- $str = trim($str);
- // get response string and code (RFC5530)
- if (preg_match("/^\[([a-z-]+)\]/i", $str, $m)) {
- $this->resultcode = strtoupper($m[1]);
- $str = trim(substr($str, strlen($m[1]) + 2));
- }
- else {
- $this->resultcode = null;
- // parse response for [APPENDUID 1204196876 3456]
- if (preg_match("/^\[APPENDUID [0-9]+ ([0-9]+)\]/i", $str, $m)) {
- $this->data['APPENDUID'] = $m[1];
- }
- // parse response for [COPYUID 1204196876 3456:3457 123:124]
- else if (preg_match("/^\[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\]/i", $str, $m)) {
- $this->data['COPYUID'] = array($m[1], $m[2]);
- }
- }
- $this->result = $str;
-
- if ($this->errornum != self::ERROR_OK) {
- $this->error = $err_prefix ? $err_prefix.$str : $str;
- }
- }
-
- return $this->errornum;
- }
- return self::ERROR_UNKNOWN;
- }
-
- /**
- * Checks connection stream state.
- *
- * @return bool True if connection is closed
- */
- private function eof()
- {
- if (!is_resource($this->fp)) {
- return true;
- }
-
- // If a connection opened by fsockopen() wasn't closed
- // by the server, feof() will hang.
- $start = microtime(true);
-
- if (feof($this->fp) ||
- ($this->prefs['timeout'] && (microtime(true) - $start > $this->prefs['timeout']))
- ) {
- $this->closeSocket();
- return true;
- }
-
- return false;
- }
-
- /**
- * Closes connection stream.
- */
- private function closeSocket()
- {
- @fclose($this->fp);
- $this->fp = null;
- }
-
- /**
- * Error code/message setter.
- */
- function setError($code, $msg='')
- {
- $this->errornum = $code;
- $this->error = $msg;
- }
-
- /**
- * Checks response status.
- * Checks if command response line starts with specified prefix (or * BYE/BAD)
- *
- * @param string $string Response text
- * @param string $match Prefix to match with (case-sensitive)
- * @param bool $error Enables BYE/BAD checking
- * @param bool $nonempty Enables empty response checking
- *
- * @return bool True any check is true or connection is closed.
- */
- function startsWith($string, $match, $error=false, $nonempty=false)
- {
- if (!$this->fp) {
- return true;
- }
- if (strncmp($string, $match, strlen($match)) == 0) {
- return true;
- }
- if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) {
- if (strtoupper($m[1]) == 'BYE') {
- $this->closeSocket();
- }
- return true;
- }
- if ($nonempty && !strlen($string)) {
- return true;
- }
- return false;
- }
-
- private function hasCapability($name)
- {
- if (empty($this->capability) || $name == '') {
- return false;
- }
-
- if (in_array($name, $this->capability)) {
- return true;
- }
- else if (strpos($name, '=')) {
- return false;
- }
-
- $result = array();
- foreach ($this->capability as $cap) {
- $entry = explode('=', $cap);
- if ($entry[0] == $name) {
- $result[] = $entry[1];
- }
- }
-
- return !empty($result) ? $result : false;
- }
-
- /**
- * Capabilities checker
- *
- * @param string $name Capability name
- *
- * @return mixed Capability values array for key=value pairs, true/false for others
- */
- function getCapability($name)
- {
- $result = $this->hasCapability($name);
-
- if (!empty($result)) {
- return $result;
- }
- else if ($this->capability_readed) {
- return false;
- }
-
- // get capabilities (only once) because initial
- // optional CAPABILITY response may differ
- $result = $this->execute('CAPABILITY');
-
- if ($result[0] == self::ERROR_OK) {
- $this->parseCapability($result[1]);
- }
-
- $this->capability_readed = true;
-
- return $this->hasCapability($name);
- }
-
- function clearCapability()
- {
- $this->capability = array();
- $this->capability_readed = false;
- }
-
- /**
- * DIGEST-MD5/CRAM-MD5/PLAIN Authentication
- *
- * @param string $user
- * @param string $pass
- * @param string $type Authentication type (PLAIN/CRAM-MD5/DIGEST-MD5)
- *
- * @return resource Connection resourse on success, error code on error
- */
- function authenticate($user, $pass, $type='PLAIN')
- {
- if ($type == 'CRAM-MD5' || $type == 'DIGEST-MD5') {
- if ($type == 'DIGEST-MD5' && !class_exists('Auth_SASL')) {
- $this->setError(self::ERROR_BYE,
- "The Auth_SASL package is required for DIGEST-MD5 authentication");
- return self::ERROR_BAD;
- }
-
- $this->putLine($this->nextTag() . " AUTHENTICATE $type");
- $line = trim($this->readReply());
-
- if ($line[0] == '+') {
- $challenge = substr($line, 2);
- }
- else {
- return $this->parseResult($line);
- }
-
- if ($type == 'CRAM-MD5') {
- // RFC2195: CRAM-MD5
- $ipad = '';
- $opad = '';
-
- // initialize ipad, opad
- for ($i=0; $i<64; $i++) {
- $ipad .= chr(0x36);
- $opad .= chr(0x5C);
- }
-
- // pad $pass so it's 64 bytes
- $padLen = 64 - strlen($pass);
- for ($i=0; $i<$padLen; $i++) {
- $pass .= chr(0);
- }
-
- // generate hash
- $hash = md5($this->_xor($pass, $opad) . pack("H*",
- md5($this->_xor($pass, $ipad) . base64_decode($challenge))));
- $reply = base64_encode($user . ' ' . $hash);
-
- // send result
- $this->putLine($reply);
- }
- else {
- // RFC2831: DIGEST-MD5
- // proxy authorization
- if (!empty($this->prefs['auth_cid'])) {
- $authc = $this->prefs['auth_cid'];
- $pass = $this->prefs['auth_pw'];
- }
- else {
- $authc = $user;
- $user = '';
- }
- $auth_sasl = Auth_SASL::factory('digestmd5');
- $reply = base64_encode($auth_sasl->getResponse($authc, $pass,
- base64_decode($challenge), $this->host, 'imap', $user));
-
- // send result
- $this->putLine($reply);
- $line = trim($this->readReply());
-
- if ($line[0] == '+') {
- $challenge = substr($line, 2);
- }
- else {
- return $this->parseResult($line);
- }
-
- // check response
- $challenge = base64_decode($challenge);
- if (strpos($challenge, 'rspauth=') === false) {
- $this->setError(self::ERROR_BAD,
- "Unexpected response from server to DIGEST-MD5 response");
- return self::ERROR_BAD;
- }
-
- $this->putLine('');
- }
-
- $line = $this->readReply();
- $result = $this->parseResult($line);
- }
- else { // PLAIN
- // proxy authorization
- if (!empty($this->prefs['auth_cid'])) {
- $authc = $this->prefs['auth_cid'];
- $pass = $this->prefs['auth_pw'];
- }
- else {
- $authc = $user;
- $user = '';
- }
-
- $reply = base64_encode($user . chr(0) . $authc . chr(0) . $pass);
-
- // RFC 4959 (SASL-IR): save one round trip
- if ($this->getCapability('SASL-IR')) {
- list($result, $line) = $this->execute("AUTHENTICATE PLAIN", array($reply),
- self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY);
- }
- else {
- $this->putLine($this->nextTag() . " AUTHENTICATE PLAIN");
- $line = trim($this->readReply());
-
- if ($line[0] != '+') {
- return $this->parseResult($line);
- }
-
- // send result, get reply and process it
- $this->putLine($reply);
- $line = $this->readReply();
- $result = $this->parseResult($line);
- }
- }
-
- if ($result == self::ERROR_OK) {
- // optional CAPABILITY response
- if ($line && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
- $this->parseCapability($matches[1], true);
- }
- return $this->fp;
- }
- else {
- $this->setError($result, "AUTHENTICATE $type: $line");
- }
-
- return $result;
- }
-
- /**
- * LOGIN Authentication
- *
- * @param string $user
- * @param string $pass
- *
- * @return resource Connection resourse on success, error code on error
- */
- function login($user, $password)
- {
- list($code, $response) = $this->execute('LOGIN', array(
- $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY);
-
- // re-set capabilities list if untagged CAPABILITY response provided
- if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) {
- $this->parseCapability($matches[1], true);
- }
-
- if ($code == self::ERROR_OK) {
- return $this->fp;
- }
-
- return $code;
- }
-
- /**
- * Detects hierarchy delimiter
- *
- * @return string The delimiter
- */
- function getHierarchyDelimiter()
- {
- if ($this->prefs['delimiter']) {
- return $this->prefs['delimiter'];
- }
-
- // try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
- list($code, $response) = $this->execute('LIST',
- array($this->escape(''), $this->escape('')));
-
- if ($code == self::ERROR_OK) {
- $args = $this->tokenizeResponse($response, 4);
- $delimiter = $args[3];
-
- if (strlen($delimiter) > 0) {
- return ($this->prefs['delimiter'] = $delimiter);
- }
- }
-
- return NULL;
- }
-
- /**
- * NAMESPACE handler (RFC 2342)
- *
- * @return array Namespace data hash (personal, other, shared)
- */
- function getNamespace()
- {
- if (array_key_exists('namespace', $this->prefs)) {
- return $this->prefs['namespace'];
- }
-
- if (!$this->getCapability('NAMESPACE')) {
- return self::ERROR_BAD;
- }
-
- list($code, $response) = $this->execute('NAMESPACE');
-
- if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) {
- $data = $this->tokenizeResponse(substr($response, 11));
- }
-
- if (!is_array($data)) {
- return $code;
- }
-
- $this->prefs['namespace'] = array(
- 'personal' => $data[0],
- 'other' => $data[1],
- 'shared' => $data[2],
- );
-
- return $this->prefs['namespace'];
- }
-
- /**
- * Connects to IMAP server and authenticates.
- *
- * @param string $host Server hostname or IP
- * @param string $user User name
- * @param string $password Password
- * @param array $options Connection and class options
- *
- * @return bool True on success, False on failure
- */
- function connect($host, $user, $password, $options=null)
- {
- // set options
- if (is_array($options)) {
- $this->prefs = $options;
- }
- // set auth method
- if (!empty($this->prefs['auth_type'])) {
- $auth_method = strtoupper($this->prefs['auth_type']);
- } else {
- $auth_method = 'CHECK';
- }
-
- $result = false;
-
- // initialize connection
- $this->error = '';
- $this->errornum = self::ERROR_OK;
- $this->selected = null;
- $this->user = $user;
- $this->host = $host;
- $this->logged = false;
-
- // check input
- if (empty($host)) {
- $this->setError(self::ERROR_BAD, "Empty host");
- return false;
- }
- if (empty($user)) {
- $this->setError(self::ERROR_NO, "Empty user");
- return false;
- }
- if (empty($password)) {
- $this->setError(self::ERROR_NO, "Empty password");
- return false;
- }
-
- if (!$this->prefs['port']) {
- $this->prefs['port'] = 143;
- }
- // check for SSL
- if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') {
- $host = $this->prefs['ssl_mode'] . '://' . $host;
- }
-
- if ($this->prefs['timeout'] <= 0) {
- $this->prefs['timeout'] = ini_get('default_socket_timeout');
- }
-
- // Connect
- $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
-
- if (!$this->fp) {
- $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
- return false;
- }
-
- if ($this->prefs['timeout'] > 0)
- stream_set_timeout($this->fp, $this->prefs['timeout']);
-
- $line = trim(fgets($this->fp, 8192));
-
- if ($this->_debug) {
- // set connection identifier for debug output
- preg_match('/#([0-9]+)/', (string)$this->fp, $m);
- $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4));
-
- if ($line)
- $this->debug('S: '. $line);
- }
-
- // Connected to wrong port or connection error?
- if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
- if ($line)
- $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line);
- else
- $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']);
-
- $this->setError(self::ERROR_BAD, $error);
- $this->closeConnection();
- return false;
- }
-
- // RFC3501 [7.1] optional CAPABILITY response
- if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
- $this->parseCapability($matches[1], true);
- }
-
- // TLS connection
- if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {
- if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
- $res = $this->execute('STARTTLS');
-
- if ($res[0] != self::ERROR_OK) {
- $this->closeConnection();
- return false;
- }
-
- if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
- $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
- $this->closeConnection();
- return false;
- }
-
- // Now we're secure, capabilities need to be reread
- $this->clearCapability();
- }
- }
-
- // Send ID info
- if (!empty($this->prefs['ident']) && $this->getCapability('ID')) {
- $this->id($this->prefs['ident']);
- }
-
- $auth_methods = array();
- $result = null;
-
- // check for supported auth methods
- if ($auth_method == 'CHECK') {
- if ($auth_caps = $this->getCapability('AUTH')) {
- $auth_methods = $auth_caps;
- }
- // RFC 2595 (LOGINDISABLED) LOGIN disabled when connection is not secure
- $login_disabled = $this->getCapability('LOGINDISABLED');
- if (($key = array_search('LOGIN', $auth_methods)) !== false) {
- if ($login_disabled) {
- unset($auth_methods[$key]);
- }
- }
- else if (!$login_disabled) {
- $auth_methods[] = 'LOGIN';
- }
-
- // Use best (for security) supported authentication method
- foreach (array('DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN') as $auth_method) {
- if (in_array($auth_method, $auth_methods)) {
- break;
- }
- }
- }
- else {
- // Prevent from sending credentials in plain text when connection is not secure
- if ($auth_method == 'LOGIN' && $this->getCapability('LOGINDISABLED')) {
- $this->setError(self::ERROR_BAD, "Login disabled by IMAP server");
- $this->closeConnection();
- return false;
- }
- // replace AUTH with CRAM-MD5 for backward compat.
- if ($auth_method == 'AUTH') {
- $auth_method = 'CRAM-MD5';
- }
- }
-
- // pre-login capabilities can be not complete
- $this->capability_readed = false;
-
- // Authenticate
- switch ($auth_method) {
- case 'CRAM_MD5':
- $auth_method = 'CRAM-MD5';
- case 'CRAM-MD5':
- case 'DIGEST-MD5':
- case 'PLAIN':
- $result = $this->authenticate($user, $password, $auth_method);
- break;
- case 'LOGIN':
- $result = $this->login($user, $password);
- break;
- default:
- $this->setError(self::ERROR_BAD, "Configuration error. Unknown auth method: $auth_method");
- }
-
- // Connected and authenticated
- if (is_resource($result)) {
- if ($this->prefs['force_caps']) {
- $this->clearCapability();
- }
- $this->logged = true;
-
- return true;
- }
-
- $this->closeConnection();
-
- return false;
- }
-
- /**
- * Checks connection status
- *
- * @return bool True if connection is active and user is logged in, False otherwise.
- */
- function connected()
- {
- return ($this->fp && $this->logged) ? true : false;
- }
-
- /**
- * Closes connection with logout.
- */
- function closeConnection()
- {
- if ($this->putLine($this->nextTag() . ' LOGOUT')) {
- $this->readReply();
- }
-
- $this->closeSocket();
- }
-
- /**
- * Executes SELECT command (if mailbox is already not in selected state)
- *
- * @param string $mailbox Mailbox name
- * @param array $qresync_data QRESYNC data (RFC5162)
- *
- * @return boolean True on success, false on error
- */
- function select($mailbox, $qresync_data = null)
- {
- if (!strlen($mailbox)) {
- return false;
- }
-
- if ($this->selected === $mailbox) {
- return true;
- }
-/*
- Temporary commented out because Courier returns \Noselect for INBOX
- Requires more investigation
-
- if (is_array($this->data['LIST']) && is_array($opts = $this->data['LIST'][$mailbox])) {
- if (in_array('\\Noselect', $opts)) {
- return false;
- }
- }
-*/
- $params = array($this->escape($mailbox));
-
- // QRESYNC data items
- // 0. the last known UIDVALIDITY,
- // 1. the last known modification sequence,
- // 2. the optional set of known UIDs, and
- // 3. an optional parenthesized list of known sequence ranges and their
- // corresponding UIDs.
- if (!empty($qresync_data)) {
- if (!empty($qresync_data[2]))
- $qresync_data[2] = self::compressMessageSet($qresync_data[2]);
- $params[] = array('QRESYNC', $qresync_data);
- }
-
- list($code, $response) = $this->execute('SELECT', $params);
-
- if ($code == self::ERROR_OK) {
- $response = explode("\r\n", $response);
- foreach ($response as $line) {
- if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT)$/i', $line, $m)) {
- $this->data[strtoupper($m[2])] = (int) $m[1];
- }
- else if (preg_match('/^\* OK \[/i', $line, $match)) {
- $line = substr($line, 6);
- if (preg_match('/^(UIDNEXT|UIDVALIDITY|UNSEEN) ([0-9]+)/i', $line, $match)) {
- $this->data[strtoupper($match[1])] = (int) $match[2];
- }
- else if (preg_match('/^(HIGHESTMODSEQ) ([0-9]+)/i', $line, $match)) {
- $this->data[strtoupper($match[1])] = (string) $match[2];
- }
- else if (preg_match('/^(NOMODSEQ)/i', $line, $match)) {
- $this->data[strtoupper($match[1])] = true;
- }
- else if (preg_match('/^PERMANENTFLAGS \(([^\)]+)\)/iU', $line, $match)) {
- $this->data['PERMANENTFLAGS'] = explode(' ', $match[1]);
- }
- }
- // QRESYNC FETCH response (RFC5162)
- else if (preg_match('/^\* ([0-9+]) FETCH/i', $line, $match)) {
- $line = substr($line, strlen($match[0]));
- $fetch_data = $this->tokenizeResponse($line, 1);
- $data = array('id' => $match[1]);
-
- for ($i=0, $size=count($fetch_data); $i<$size; $i+=2) {
- $data[strtolower($fetch_data[$i])] = $fetch_data[$i+1];
- }
-
- $this->data['QRESYNC'][$data['uid']] = $data;
- }
- // QRESYNC VANISHED response (RFC5162)
- else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
- $line = substr($line, strlen($match[0]));
- $v_data = $this->tokenizeResponse($line, 1);
-
- $this->data['VANISHED'] = $v_data;
- }
- }
-
- $this->data['READ-WRITE'] = $this->resultcode != 'READ-ONLY';
-
- $this->selected = $mailbox;
- return true;
- }
-
- return false;
- }
-
- /**
- * Executes STATUS command
- *
- * @param string $mailbox Mailbox name
- * @param array $items Additional requested item names. By default
- * MESSAGES and UNSEEN are requested. Other defined
- * in RFC3501: UIDNEXT, UIDVALIDITY, RECENT
- *
- * @return array Status item-value hash
- * @since 0.5-beta
- */
- function status($mailbox, $items=array())
- {
- if (!strlen($mailbox)) {
- return false;
- }
-
- if (!in_array('MESSAGES', $items)) {
- $items[] = 'MESSAGES';
- }
- if (!in_array('UNSEEN', $items)) {
- $items[] = 'UNSEEN';
- }
-
- list($code, $response) = $this->execute('STATUS', array($this->escape($mailbox),
- '(' . implode(' ', (array) $items) . ')'));
-
- if ($code == self::ERROR_OK && preg_match('/\* STATUS /i', $response)) {
- $result = array();
- $response = substr($response, 9); // remove prefix "* STATUS "
-
- list($mbox, $items) = $this->tokenizeResponse($response, 2);
-
- // Fix for #1487859. Some buggy server returns not quoted
- // folder name with spaces. Let's try to handle this situation
- if (!is_array($items) && ($pos = strpos($response, '(')) !== false) {
- $response = substr($response, $pos);
- $items = $this->tokenizeResponse($response, 1);
- if (!is_array($items)) {
- return $result;
- }
- }
-
- for ($i=0, $len=count($items); $i<$len; $i += 2) {
- $result[$items[$i]] = $items[$i+1];
- }
-
- $this->data['STATUS:'.$mailbox] = $result;
-
- return $result;
- }
-
- return false;
- }
-
- /**
- * Executes EXPUNGE command
- *
- * @param string $mailbox Mailbox name
- * @param string $messages Message UIDs to expunge
- *
- * @return boolean True on success, False on error
- */
- function expunge($mailbox, $messages=NULL)
- {
- if (!$this->select($mailbox)) {
- return false;
- }
-
- if (!$this->data['READ-WRITE']) {
- $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'EXPUNGE');
- return false;
- }
-
- // Clear internal status cache
- unset($this->data['STATUS:'.$mailbox]);
-
- if ($messages)
- $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
- else
- $result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE);
-
- if ($result == self::ERROR_OK) {
- $this->selected = null; // state has changed, need to reselect
- return true;
- }
-
- return false;
- }
-
- /**
- * Executes CLOSE command
- *
- * @return boolean True on success, False on error
- * @since 0.5
- */
- function close()
- {
- $result = $this->execute('CLOSE', NULL, self::COMMAND_NORESPONSE);
-
- if ($result == self::ERROR_OK) {
- $this->selected = null;
- return true;
- }
-
- return false;
- }
-
- /**
- * Folder subscription (SUBSCRIBE)
- *
- * @param string $mailbox Mailbox name
- *
- * @return boolean True on success, False on error
- */
- function subscribe($mailbox)
- {
- $result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Folder unsubscription (UNSUBSCRIBE)
- *
- * @param string $mailbox Mailbox name
- *
- * @return boolean True on success, False on error
- */
- function unsubscribe($mailbox)
- {
- $result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Folder creation (CREATE)
- *
- * @param string $mailbox Mailbox name
- *
- * @return bool True on success, False on error
- */
- function createFolder($mailbox)
- {
- $result = $this->execute('CREATE', array($this->escape($mailbox)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Folder renaming (RENAME)
- *
- * @param string $mailbox Mailbox name
- *
- * @return bool True on success, False on error
- */
- function renameFolder($from, $to)
- {
- $result = $this->execute('RENAME', array($this->escape($from), $this->escape($to)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Executes DELETE command
- *
- * @param string $mailbox Mailbox name
- *
- * @return boolean True on success, False on error
- */
- function deleteFolder($mailbox)
- {
- $result = $this->execute('DELETE', array($this->escape($mailbox)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Removes all messages in a folder
- *
- * @param string $mailbox Mailbox name
- *
- * @return boolean True on success, False on error
- */
- function clearFolder($mailbox)
- {
- $num_in_trash = $this->countMessages($mailbox);
- if ($num_in_trash > 0) {
- $res = $this->flag($mailbox, '1:*', 'DELETED');
- }
-
- if ($res) {
- if ($this->selected === $mailbox)
- $res = $this->close();
- else
- $res = $this->expunge($mailbox);
- }
-
- return $res;
- }
-
- /**
- * Returns list of mailboxes
- *
- * @param string $ref Reference name
- * @param string $mailbox Mailbox name
- * @param array $status_opts (see self::_listMailboxes)
- * @param array $select_opts (see self::_listMailboxes)
- *
- * @return array List of mailboxes or hash of options if $status_opts argument
- * is non-empty.
- */
- function listMailboxes($ref, $mailbox, $status_opts=array(), $select_opts=array())
- {
- return $this->_listMailboxes($ref, $mailbox, false, $status_opts, $select_opts);
- }
-
- /**
- * Returns list of subscribed mailboxes
- *
- * @param string $ref Reference name
- * @param string $mailbox Mailbox name
- * @param array $status_opts (see self::_listMailboxes)
- *
- * @return array List of mailboxes or hash of options if $status_opts argument
- * is non-empty.
- */
- function listSubscribed($ref, $mailbox, $status_opts=array())
- {
- return $this->_listMailboxes($ref, $mailbox, true, $status_opts, NULL);
- }
-
- /**
- * IMAP LIST/LSUB command
- *
- * @param string $ref Reference name
- * @param string $mailbox Mailbox name
- * @param bool $subscribed Enables returning subscribed mailboxes only
- * @param array $status_opts List of STATUS options (RFC5819: LIST-STATUS)
- * Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN
- * @param array $select_opts List of selection options (RFC5258: LIST-EXTENDED)
- * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE
- *
- * @return array List of mailboxes or hash of options if $status_ops argument
- * is non-empty.
- */
- private function _listMailboxes($ref, $mailbox, $subscribed=false,
- $status_opts=array(), $select_opts=array())
- {
- if (!strlen($mailbox)) {
- $mailbox = '*';
- }
-
- $args = array();
-
- if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) {
- $select_opts = (array) $select_opts;
-
- $args[] = '(' . implode(' ', $select_opts) . ')';
- }
-
- $args[] = $this->escape($ref);
- $args[] = $this->escape($mailbox);
-
- if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) {
- $status_opts = (array) $status_opts;
- $lstatus = true;
-
- $args[] = 'RETURN (STATUS (' . implode(' ', $status_opts) . '))';
- }
-
- list($code, $response) = $this->execute($subscribed ? 'LSUB' : 'LIST', $args);
-
- if ($code == self::ERROR_OK) {
- $folders = array();
- $last = 0;
- $pos = 0;
- $response .= "\r\n";
-
- while ($pos = strpos($response, "\r\n", $pos+1)) {
- // literal string, not real end-of-command-line
- if ($response[$pos-1] == '}') {
- continue;
- }
-
- $line = substr($response, $last, $pos - $last);
- $last = $pos + 2;
-
- if (!preg_match('/^\* (LIST|LSUB|STATUS) /i', $line, $m)) {
- continue;
- }
- $cmd = strtoupper($m[1]);
- $line = substr($line, strlen($m[0]));
-
- // * LIST (<options>) <delimiter> <mailbox>
- if ($cmd == 'LIST' || $cmd == 'LSUB') {
- list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3);
-
- // Add to result array
- if (!$lstatus) {
- $folders[] = $mailbox;
- }
- else {
- $folders[$mailbox] = array();
- }
-
- // store LSUB options only if not empty, this way
- // we can detect a situation when LIST doesn't return specified folder
- if (!empty($opts) || $cmd == 'LIST') {
- // Add to options array
- if (empty($this->data['LIST'][$mailbox]))
- $this->data['LIST'][$mailbox] = $opts;
- else if (!empty($opts))
- $this->data['LIST'][$mailbox] = array_unique(array_merge(
- $this->data['LIST'][$mailbox], $opts));
- }
- }
- // * STATUS <mailbox> (<result>)
- else if ($cmd == 'STATUS') {
- list($mailbox, $status) = $this->tokenizeResponse($line, 2);
-
- for ($i=0, $len=count($status); $i<$len; $i += 2) {
- list($name, $value) = $this->tokenizeResponse($status, 2);
- $folders[$mailbox][$name] = $value;
- }
- }
- }
-
- return $folders;
- }
-
- return false;
- }
-
- /**
- * Returns count of all messages in a folder
- *
- * @param string $mailbox Mailbox name
- *
- * @return int Number of messages, False on error
- */
- function countMessages($mailbox, $refresh = false)
- {
- if ($refresh) {
- $this->selected = null;
- }
-
- if ($this->selected === $mailbox) {
- return $this->data['EXISTS'];
- }
-
- // Check internal cache
- $cache = $this->data['STATUS:'.$mailbox];
- if (!empty($cache) && isset($cache['MESSAGES'])) {
- return (int) $cache['MESSAGES'];
- }
-
- // Try STATUS (should be faster than SELECT)
- $counts = $this->status($mailbox);
- if (is_array($counts)) {
- return (int) $counts['MESSAGES'];
- }
-
- return false;
- }
-
- /**
- * Returns count of messages with \Recent flag in a folder
- *
- * @param string $mailbox Mailbox name
- *
- * @return int Number of messages, False on error
- */
- function countRecent($mailbox)
- {
- if (!strlen($mailbox)) {
- $mailbox = 'INBOX';
- }
-
- $this->select($mailbox);
-
- if ($this->selected === $mailbox) {
- return $this->data['RECENT'];
- }
-
- return false;
- }
-
- /**
- * Returns count of messages without \Seen flag in a specified folder
- *
- * @param string $mailbox Mailbox name
- *
- * @return int Number of messages, False on error
- */
- function countUnseen($mailbox)
- {
- // Check internal cache
- $cache = $this->data['STATUS:'.$mailbox];
- if (!empty($cache) && isset($cache['UNSEEN'])) {
- return (int) $cache['UNSEEN'];
- }
-
- // Try STATUS (should be faster than SELECT+SEARCH)
- $counts = $this->status($mailbox);
- if (is_array($counts)) {
- return (int) $counts['UNSEEN'];
- }
-
- // Invoke SEARCH as a fallback
- $index = $this->search($mailbox, 'ALL UNSEEN', false, array('COUNT'));
- if (!$index->is_error()) {
- return $index->count();
- }
-
- return false;
- }
-
- /**
- * Executes ID command (RFC2971)
- *
- * @param array $items Client identification information key/value hash
- *
- * @return array Server identification information key/value hash
- * @since 0.6
- */
- function id($items=array())
- {
- if (is_array($items) && !empty($items)) {
- foreach ($items as $key => $value) {
- $args[] = $this->escape($key, true);
- $args[] = $this->escape($value, true);
- }
- }
-
- list($code, $response) = $this->execute('ID', array(
- !empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null)
- ));
-
-
- if ($code == self::ERROR_OK && preg_match('/\* ID /i', $response)) {
- $response = substr($response, 5); // remove prefix "* ID "
- $items = $this->tokenizeResponse($response, 1);
- $result = null;
-
- for ($i=0, $len=count($items); $i<$len; $i += 2) {
- $result[$items[$i]] = $items[$i+1];
- }
-
- return $result;
- }
-
- return false;
- }
-
- /**
- * Executes ENABLE command (RFC5161)
- *
- * @param mixed $extension Extension name to enable (or array of names)
- *
- * @return array|bool List of enabled extensions, False on error
- * @since 0.6
- */
- function enable($extension)
- {
- if (empty($extension)) {
- return false;
- }
-
- if (!$this->hasCapability('ENABLE')) {
- return false;
- }
-
- if (!is_array($extension)) {
- $extension = array($extension);
- }
-
- if (!empty($this->extensions_enabled)) {
- // check if all extensions are already enabled
- $diff = array_diff($extension, $this->extensions_enabled);
-
- if (empty($diff)) {
- return $extension;
- }
-
- // Make sure the mailbox isn't selected, before enabling extension(s)
- if ($this->selected !== null) {
- $this->close();
- }
- }
-
- list($code, $response) = $this->execute('ENABLE', $extension);
-
- if ($code == self::ERROR_OK && preg_match('/\* ENABLED /i', $response)) {
- $response = substr($response, 10); // remove prefix "* ENABLED "
- $result = (array) $this->tokenizeResponse($response);
-
- $this->extensions_enabled = array_unique(array_merge((array)$this->extensions_enabled, $result));
-
- return $this->extensions_enabled;
- }
-
- return false;
- }
-
- /**
- * Executes SORT command
- *
- * @param string $mailbox Mailbox name
- * @param string $field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO)
- * @param string $add Searching criteria
- * @param bool $return_uid Enables UID SORT usage
- * @param string $encoding Character set
- *
- * @return rcube_result_index Response data
- */
- function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII')
- {
- $field = strtoupper($field);
- if ($field == 'INTERNALDATE') {
- $field = 'ARRIVAL';
- }
-
- $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
- 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
-
- if (!$fields[$field]) {
- return new rcube_result_index($mailbox);
- }
-
- if (!$this->select($mailbox)) {
- return new rcube_result_index($mailbox);
- }
-
- // RFC 5957: SORT=DISPLAY
- if (($field == 'FROM' || $field == 'TO') && $this->getCapability('SORT=DISPLAY')) {
- $field = 'DISPLAY' . $field;
- }
-
- // message IDs
- if (!empty($add))
- $add = $this->compressMessageSet($add);
-
- list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT',
- array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : '')));
-
- if ($code != self::ERROR_OK) {
- $response = null;
- }
-
- return new rcube_result_index($mailbox, $response);
- }
-
- /**
- * Executes THREAD command
- *
- * @param string $mailbox Mailbox name
- * @param string $algorithm Threading algorithm (ORDEREDSUBJECT, REFERENCES, REFS)
- * @param string $criteria Searching criteria
- * @param bool $return_uid Enables UIDs in result instead of sequence numbers
- * @param string $encoding Character set
- *
- * @return rcube_result_thread Thread data
- */
- function thread($mailbox, $algorithm='REFERENCES', $criteria='', $return_uid=false, $encoding='US-ASCII')
- {
- $old_sel = $this->selected;
-
- if (!$this->select($mailbox)) {
- return new rcube_result_thread($mailbox);
- }
-
- // return empty result when folder is empty and we're just after SELECT
- if ($old_sel != $mailbox && !$this->data['EXISTS']) {
- return new rcube_result_thread($mailbox);
- }
-
- $encoding = $encoding ? trim($encoding) : 'US-ASCII';
- $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES';
- $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL';
- $data = '';
-
- list($code, $response) = $this->execute($return_uid ? 'UID THREAD' : 'THREAD',
- array($algorithm, $encoding, $criteria));
-
- if ($code != self::ERROR_OK) {
- $response = null;
- }
-
- return new rcube_result_thread($mailbox, $response);
- }
-
- /**
- * Executes SEARCH command
- *
- * @param string $mailbox Mailbox name
- * @param string $criteria Searching criteria
- * @param bool $return_uid Enable UID in result instead of sequence ID
- * @param array $items Return items (MIN, MAX, COUNT, ALL)
- *
- * @return rcube_result_index Result data
- */
- function search($mailbox, $criteria, $return_uid=false, $items=array())
- {
- $old_sel = $this->selected;
-
- if (!$this->select($mailbox)) {
- return new rcube_result_index($mailbox);
- }
-
- // return empty result when folder is empty and we're just after SELECT
- if ($old_sel != $mailbox && !$this->data['EXISTS']) {
- return new rcube_result_index($mailbox, '* SEARCH');
- }
-
- // If ESEARCH is supported always use ALL
- // but not when items are specified or using simple id2uid search
- if (empty($items) && preg_match('/[^0-9]/', $criteria)) {
- $items = array('ALL');
- }
-
- $esearch = empty($items) ? false : $this->getCapability('ESEARCH');
- $criteria = trim($criteria);
- $params = '';
-
- // RFC4731: ESEARCH
- if (!empty($items) && $esearch) {
- $params .= 'RETURN (' . implode(' ', $items) . ')';
- }
-
- if (!empty($criteria)) {
- $modseq = stripos($criteria, 'MODSEQ') !== false;
- $params .= ($params ? ' ' : '') . $criteria;
- }
- else {
- $params .= 'ALL';
- }
-
- list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH',
- array($params));
-
- if ($code != self::ERROR_OK) {
- $response = null;
- }
-
- return new rcube_result_index($mailbox, $response);
- }
-
- /**
- * Simulates SORT command by using FETCH and sorting.
- *
- * @param string $mailbox Mailbox name
- * @param string|array $message_set Searching criteria (list of messages to return)
- * @param string $index_field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO)
- * @param bool $skip_deleted Makes that DELETED messages will be skipped
- * @param bool $uidfetch Enables UID FETCH usage
- * @param bool $return_uid Enables returning UIDs instead of IDs
- *
- * @return rcube_result_index Response data
- */
- function index($mailbox, $message_set, $index_field='', $skip_deleted=true,
- $uidfetch=false, $return_uid=false)
- {
- $msg_index = $this->fetchHeaderIndex($mailbox, $message_set,
- $index_field, $skip_deleted, $uidfetch, $return_uid);
-
- if (!empty($msg_index)) {
- asort($msg_index); // ASC
- $msg_index = array_keys($msg_index);
- $msg_index = '* SEARCH ' . implode(' ', $msg_index);
- }
- else {
- $msg_index = is_array($msg_index) ? '* SEARCH' : null;
- }
-
- return new rcube_result_index($mailbox, $msg_index);
- }
-
- function fetchHeaderIndex($mailbox, $message_set, $index_field='', $skip_deleted=true,
- $uidfetch=false, $return_uid=false)
- {
- if (is_array($message_set)) {
- if (!($message_set = $this->compressMessageSet($message_set)))
- return false;
- } else {
- list($from_idx, $to_idx) = explode(':', $message_set);
- if (empty($message_set) ||
- (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) {
- return false;
- }
- }
-
- $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field);
-
- $fields_a['DATE'] = 1;
- $fields_a['INTERNALDATE'] = 4;
- $fields_a['ARRIVAL'] = 4;
- $fields_a['FROM'] = 1;
- $fields_a['REPLY-TO'] = 1;
- $fields_a['SENDER'] = 1;
- $fields_a['TO'] = 1;
- $fields_a['CC'] = 1;
- $fields_a['SUBJECT'] = 1;
- $fields_a['UID'] = 2;
- $fields_a['SIZE'] = 2;
- $fields_a['SEEN'] = 3;
- $fields_a['RECENT'] = 3;
- $fields_a['DELETED'] = 3;
-
- if (!($mode = $fields_a[$index_field])) {
- return false;
- }
-
- /* Do "SELECT" command */
- if (!$this->select($mailbox)) {
- return false;
- }
-
- // build FETCH command string
- $key = $this->nextTag();
- $cmd = $uidfetch ? 'UID FETCH' : 'FETCH';
- $fields = array();
-
- if ($return_uid)
- $fields[] = 'UID';
- if ($skip_deleted)
- $fields[] = 'FLAGS';
-
- if ($mode == 1) {
- if ($index_field == 'DATE')
- $fields[] = 'INTERNALDATE';
- $fields[] = "BODY.PEEK[HEADER.FIELDS ($index_field)]";
- }
- else if ($mode == 2) {
- if ($index_field == 'SIZE')
- $fields[] = 'RFC822.SIZE';
- else if (!$return_uid || $index_field != 'UID')
- $fields[] = $index_field;
- }
- else if ($mode == 3 && !$skip_deleted)
- $fields[] = 'FLAGS';
- else if ($mode == 4)
- $fields[] = 'INTERNALDATE';
-
- $request = "$key $cmd $message_set (" . implode(' ', $fields) . ")";
-
- if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
- return false;
- }
-
- $result = array();
-
- do {
- $line = rtrim($this->readLine(200));
- $line = $this->multLine($line);
-
- if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
- $id = $m[1];
- $flags = NULL;
-
- if ($return_uid) {
- if (preg_match('/UID ([0-9]+)/', $line, $matches))
- $id = (int) $matches[1];
- else
- continue;
- }
- if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
- $flags = explode(' ', strtoupper($matches[1]));
- if (in_array('\\DELETED', $flags)) {
- $deleted[$id] = $id;
- continue;
- }
- }
-
- if ($mode == 1 && $index_field == 'DATE') {
- if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) {
- $value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]);
- $value = trim($value);
- $result[$id] = $this->strToTime($value);
- }
- // non-existent/empty Date: header, use INTERNALDATE
- if (empty($result[$id])) {
- if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches))
- $result[$id] = $this->strToTime($matches[1]);
- else
- $result[$id] = 0;
- }
- } else if ($mode == 1) {
- if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) {
- $value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]);
- $result[$id] = trim($value);
- } else {
- $result[$id] = '';
- }
- } else if ($mode == 2) {
- if (preg_match('/(UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) {
- $result[$id] = trim($matches[2]);
- } else {
- $result[$id] = 0;
- }
- } else if ($mode == 3) {
- if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
- $flags = explode(' ', $matches[1]);
- }
- $result[$id] = in_array('\\'.$index_field, $flags) ? 1 : 0;
- } else if ($mode == 4) {
- if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) {
- $result[$id] = $this->strToTime($matches[1]);
- } else {
- $result[$id] = 0;
- }
- }
- }
- } while (!$this->startsWith($line, $key, true, true));
-
- return $result;
- }
-
- /**
- * Returns message sequence identifier
- *
- * @param string $mailbox Mailbox name
- * @param int $uid Message unique identifier (UID)
- *
- * @return int Message sequence identifier
- */
- function UID2ID($mailbox, $uid)
- {
- if ($uid > 0) {
- $index = $this->search($mailbox, "UID $uid");
-
- if ($index->count() == 1) {
- $arr = $index->get();
- return (int) $arr[0];
- }
- }
- return null;
- }
-
- /**
- * Returns message unique identifier (UID)
- *
- * @param string $mailbox Mailbox name
- * @param int $uid Message sequence identifier
- *
- * @return int Message unique identifier
- */
- function ID2UID($mailbox, $id)
- {
- if (empty($id) || $id < 0) {
- return null;
- }
-
- if (!$this->select($mailbox)) {
- return null;
- }
-
- $index = $this->search($mailbox, $id, true);
-
- if ($index->count() == 1) {
- $arr = $index->get();
- return (int) $arr[0];
- }
-
- return null;
- }
-
- /**
- * Sets flag of the message(s)
- *
- * @param string $mailbox Mailbox name
- * @param string|array $messages Message UID(s)
- * @param string $flag Flag name
- *
- * @return bool True on success, False on failure
- */
- function flag($mailbox, $messages, $flag) {
- return $this->modFlag($mailbox, $messages, $flag, '+');
- }
-
- /**
- * Unsets flag of the message(s)
- *
- * @param string $mailbox Mailbox name
- * @param string|array $messages Message UID(s)
- * @param string $flag Flag name
- *
- * @return bool True on success, False on failure
- */
- function unflag($mailbox, $messages, $flag) {
- return $this->modFlag($mailbox, $messages, $flag, '-');
- }
-
- /**
- * Changes flag of the message(s)
- *
- * @param string $mailbox Mailbox name
- * @param string|array $messages Message UID(s)
- * @param string $flag Flag name
- * @param string $mod Modifier [+|-]. Default: "+".
- *
- * @return bool True on success, False on failure
- */
- private function modFlag($mailbox, $messages, $flag, $mod = '+')
- {
- if ($mod != '+' && $mod != '-') {
- $mod = '+';
- }
-
- if (!$this->select($mailbox)) {
- return false;
- }
-
- if (!$this->data['READ-WRITE']) {
- $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
- return false;
- }
-
- // Clear internal status cache
- if ($flag == 'SEEN') {
- unset($this->data['STATUS:'.$mailbox]['UNSEEN']);
- }
-
- $flag = $this->flags[strtoupper($flag)];
- $result = $this->execute('UID STORE', array(
- $this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Copies message(s) from one folder to another
- *
- * @param string|array $messages Message UID(s)
- * @param string $from Mailbox name
- * @param string $to Destination mailbox name
- *
- * @return bool True on success, False on failure
- */
- function copy($messages, $from, $to)
- {
- // Clear last COPYUID data
- unset($this->data['COPYUID']);
-
- if (!$this->select($from)) {
- return false;
- }
-
- // Clear internal status cache
- unset($this->data['STATUS:'.$to]);
-
- $result = $this->execute('UID COPY', array(
- $this->compressMessageSet($messages), $this->escape($to)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Moves message(s) from one folder to another.
- * Original message(s) will be marked as deleted.
- *
- * @param string|array $messages Message UID(s)
- * @param string $from Mailbox name
- * @param string $to Destination mailbox name
- *
- * @return bool True on success, False on failure
- */
- function move($messages, $from, $to)
- {
- if (!$this->select($from)) {
- return false;
- }
-
- if (!$this->data['READ-WRITE']) {
- $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
- return false;
- }
-
- $r = $this->copy($messages, $from, $to);
-
- if ($r) {
- // Clear internal status cache
- unset($this->data['STATUS:'.$from]);
-
- return $this->flag($from, $messages, 'DELETED');
- }
- return $r;
- }
-
- /**
- * FETCH command (RFC3501)
- *
- * @param string $mailbox Mailbox name
- * @param mixed $message_set Message(s) sequence identifier(s) or UID(s)
- * @param bool $is_uid True if $message_set contains UIDs
- * @param array $query_items FETCH command data items
- * @param string $mod_seq Modification sequence for CHANGEDSINCE (RFC4551) query
- * @param bool $vanished Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query
- *
- * @return array List of rcube_message_header elements, False on error
- * @since 0.6
- */
- function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(),
- $mod_seq = null, $vanished = false)
- {
- if (!$this->select($mailbox)) {
- return false;
- }
-
- $message_set = $this->compressMessageSet($message_set);
- $result = array();
-
- $key = $this->nextTag();
- $request = $key . ($is_uid ? ' UID' : '') . " FETCH $message_set ";
- $request .= "(" . implode(' ', $query_items) . ")";
-
- if ($mod_seq !== null && $this->hasCapability('CONDSTORE')) {
- $request .= " (CHANGEDSINCE $mod_seq" . ($vanished ? " VANISHED" : '') .")";
- }
-
- if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
- return false;
- }
-
- do {
- $line = $this->readLine(4096);
-
- if (!$line)
- break;
-
- // Sample reply line:
- // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen)
- // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...)
- // BODY[HEADER.FIELDS ...
-
- if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
- $id = intval($m[1]);
-
- $result[$id] = new rcube_message_header;
- $result[$id]->id = $id;
- $result[$id]->subject = '';
- $result[$id]->messageID = 'mid:' . $id;
-
- $headers = null;
- $lines = array();
- $line = substr($line, strlen($m[0]) + 2);
- $ln = 0;
-
- // get complete entry
- while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
- $bytes = $m[1];
- $out = '';
-
- while (strlen($out) < $bytes) {
- $out = $this->readBytes($bytes);
- if ($out === NULL)
- break;
- $line .= $out;
- }
-
- $str = $this->readLine(4096);
- if ($str === false)
- break;
-
- $line .= $str;
- }
-
- // Tokenize response and assign to object properties
- while (list($name, $value) = $this->tokenizeResponse($line, 2)) {
- if ($name == 'UID') {
- $result[$id]->uid = intval($value);
- }
- else if ($name == 'RFC822.SIZE') {
- $result[$id]->size = intval($value);
- }
- else if ($name == 'RFC822.TEXT') {
- $result[$id]->body = $value;
- }
- else if ($name == 'INTERNALDATE') {
- $result[$id]->internaldate = $value;
- $result[$id]->date = $value;
- $result[$id]->timestamp = $this->StrToTime($value);
- }
- else if ($name == 'FLAGS') {
- if (!empty($value)) {
- foreach ((array)$value as $flag) {
- $flag = str_replace(array('$', '\\'), '', $flag);
- $flag = strtoupper($flag);
-
- $result[$id]->flags[$flag] = true;
- }
- }
- }
- else if ($name == 'MODSEQ') {
- $result[$id]->modseq = $value[0];
- }
- else if ($name == 'ENVELOPE') {
- $result[$id]->envelope = $value;
- }
- else if ($name == 'BODYSTRUCTURE' || ($name == 'BODY' && count($value) > 2)) {
- if (!is_array($value[0]) && (strtolower($value[0]) == 'message' && strtolower($value[1]) == 'rfc822')) {
- $value = array($value);
- }
- $result[$id]->bodystructure = $value;
- }
- else if ($name == 'RFC822') {
- $result[$id]->body = $value;
- }
- else if ($name == 'BODY') {
- $body = $this->tokenizeResponse($line, 1);
- if ($value[0] == 'HEADER.FIELDS')
- $headers = $body;
- else if (!empty($value))
- $result[$id]->bodypart[$value[0]] = $body;
- else
- $result[$id]->body = $body;
- }
- }
-
- // create array with header field:data
- if (!empty($headers)) {
- $headers = explode("\n", trim($headers));
- foreach ($headers as $hid => $resln) {
- if (ord($resln[0]) <= 32) {
- $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln);
- } else {
- $lines[++$ln] = trim($resln);
- }
- }
-
- while (list($lines_key, $str) = each($lines)) {
- list($field, $string) = explode(':', $str, 2);
-
- $field = strtolower($field);
- $string = preg_replace('/\n[\t\s]*/', ' ', trim($string));
-
- switch ($field) {
- case 'date';
- $result[$id]->date = $string;
- $result[$id]->timestamp = $this->strToTime($string);
- break;
- case 'from':
- $result[$id]->from = $string;
- break;
- case 'to':
- $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string);
- break;
- case 'subject':
- $result[$id]->subject = $string;
- break;
- case 'reply-to':
- $result[$id]->replyto = $string;
- break;
- case 'cc':
- $result[$id]->cc = $string;
- break;
- case 'bcc':
- $result[$id]->bcc = $string;
- break;
- case 'content-transfer-encoding':
- $result[$id]->encoding = $string;
- break;
- case 'content-type':
- $ctype_parts = preg_split('/[; ]+/', $string);
- $result[$id]->ctype = strtolower(array_shift($ctype_parts));
- if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) {
- $result[$id]->charset = $regs[1];
- }
- break;
- case 'in-reply-to':
- $result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string);
- break;
- case 'references':
- $result[$id]->references = $string;
- break;
- case 'return-receipt-to':
- case 'disposition-notification-to':
- case 'x-confirm-reading-to':
- $result[$id]->mdn_to = $string;
- break;
- case 'message-id':
- $result[$id]->messageID = $string;
- break;
- case 'x-priority':
- if (preg_match('/^(\d+)/', $string, $matches)) {
- $result[$id]->priority = intval($matches[1]);
- }
- break;
- default:
- if (strlen($field) > 2) {
- $result[$id]->others[$field] = $string;
- }
- break;
- }
- }
- }
- }
-
- // VANISHED response (QRESYNC RFC5162)
- // Sample: * VANISHED (EARLIER) 300:310,405,411
-
- else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
- $line = substr($line, strlen($match[0]));
- $v_data = $this->tokenizeResponse($line, 1);
-
- $this->data['VANISHED'] = $v_data;
- }
-
- } while (!$this->startsWith($line, $key, true));
-
- return $result;
- }
-
- function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add = '')
- {
- $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE');
- if ($bodystr)
- $query_items[] = 'BODYSTRUCTURE';
- $query_items[] = 'BODY.PEEK[HEADER.FIELDS ('
- . 'DATE FROM TO SUBJECT CONTENT-TYPE CC REPLY-TO LIST-POST DISPOSITION-NOTIFICATION-TO X-PRIORITY'
- . ($add ? ' ' . trim($add) : '')
- . ')]';
-
- $result = $this->fetch($mailbox, $message_set, $is_uid, $query_items);
-
- return $result;
- }
-
- function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='')
- {
- $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add);
- if (is_array($a)) {
- return array_shift($a);
- }
- return false;
- }
-
- function sortHeaders($a, $field, $flag)
- {
- if (empty($field)) {
- $field = 'uid';
- }
- else {
- $field = strtolower($field);
- }
-
- if ($field == 'date' || $field == 'internaldate') {
- $field = 'timestamp';
- }
-
- if (empty($flag)) {
- $flag = 'ASC';
- } else {
- $flag = strtoupper($flag);
- }
-
- $c = count($a);
- if ($c > 0) {
- // Strategy:
- // First, we'll create an "index" array.
- // Then, we'll use sort() on that array,
- // and use that to sort the main array.
-
- // create "index" array
- $index = array();
- reset($a);
- while (list($key, $val) = each($a)) {
- if ($field == 'timestamp') {
- $data = $this->strToTime($val->date);
- if (!$data) {
- $data = $val->timestamp;
- }
- } else {
- $data = $val->$field;
- if (is_string($data)) {
- $data = str_replace('"', '', $data);
- if ($field == 'subject') {
- $data = preg_replace('/^(Re: \s*|Fwd:\s*|Fw:\s*)+/i', '', $data);
- }
- $data = strtoupper($data);
- }
- }
- $index[$key] = $data;
- }
-
- // sort index
- if ($flag == 'ASC') {
- asort($index);
- } else {
- arsort($index);
- }
-
- // form new array based on index
- $result = array();
- reset($index);
- while (list($key, $val) = each($index)) {
- $result[$key] = $a[$key];
- }
- }
-
- return $result;
- }
-
- function fetchMIMEHeaders($mailbox, $uid, $parts, $mime=true)
- {
- if (!$this->select($mailbox)) {
- return false;
- }
-
- $result = false;
- $parts = (array) $parts;
- $key = $this->nextTag();
- $peeks = array();
- $type = $mime ? 'MIME' : 'HEADER';
-
- // format request
- foreach ($parts as $part) {
- $peeks[] = "BODY.PEEK[$part.$type]";
- }
-
- $request = "$key UID FETCH $uid (" . implode(' ', $peeks) . ')';
-
- // send request
- if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
- return false;
- }
-
- do {
- $line = $this->readLine(1024);
-
- if (preg_match('/^\* [0-9]+ FETCH [0-9UID( ]+BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) {
- $idx = $matches[1];
- $headers = '';
-
- // get complete entry
- if (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
- $bytes = $m[1];
- $out = '';
-
- while (strlen($out) < $bytes) {
- $out = $this->readBytes($bytes);
- if ($out === null)
- break;
- $headers .= $out;
- }
- }
-
- $result[$idx] = trim($headers);
- }
- } while (!$this->startsWith($line, $key, true));
-
- return $result;
- }
-
- function fetchPartHeader($mailbox, $id, $is_uid=false, $part=NULL)
- {
- $part = empty($part) ? 'HEADER' : $part.'.MIME';
-
- return $this->handlePartBody($mailbox, $id, $is_uid, $part);
- }
-
- function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL, $formatted=true)
- {
- if (!$this->select($mailbox)) {
- return false;
- }
-
- switch ($encoding) {
- case 'base64':
- $mode = 1;
- break;
- case 'quoted-printable':
- $mode = 2;
- break;
- case 'x-uuencode':
- case 'x-uue':
- case 'uue':
- case 'uuencode':
- $mode = 3;
- break;
- default:
- $mode = 0;
- }
-
- // Use BINARY extension when possible (and safe)
- $binary = $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY');
- $fetch_mode = $binary ? 'BINARY' : 'BODY';
-
- // format request
- $key = $this->nextTag();
- $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part])";
-
- // send request
- if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
- return false;
- }
-
- if ($binary) {
- $mode = -1;
- }
-
- // receive reply line
- do {
- $line = rtrim($this->readLine(1024));
- $a = explode(' ', $line);
- } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH');
-
- $len = strlen($line);
- $result = false;
-
- if ($a[2] != 'FETCH') {
- }
- // handle empty "* X FETCH ()" response
- else if ($line[$len-1] == ')' && $line[$len-2] != '(') {
- // one line response, get everything between first and last quotes
- if (substr($line, -4, 3) == 'NIL') {
- // NIL response
- $result = '';
- } else {
- $from = strpos($line, '"') + 1;
- $to = strrpos($line, '"');
- $len = $to - $from;
- $result = substr($line, $from, $len);
- }
-
- if ($mode == 1) {
- $result = base64_decode($result);
- }
- else if ($mode == 2) {
- $result = quoted_printable_decode($result);
- }
- else if ($mode == 3) {
- $result = convert_uudecode($result);
- }
-
- } else if ($line[$len-1] == '}') {
- // multi-line request, find sizes of content and receive that many bytes
- $from = strpos($line, '{') + 1;
- $to = strrpos($line, '}');
- $len = $to - $from;
- $sizeStr = substr($line, $from, $len);
- $bytes = (int)$sizeStr;
- $prev = '';
-
- while ($bytes > 0) {
- $line = $this->readLine(8192);
-
- if ($line === NULL) {
- break;
- }
-
- $len = strlen($line);
-
- if ($len > $bytes) {
- $line = substr($line, 0, $bytes);
- $len = strlen($line);
- }
- $bytes -= $len;
-
- // BASE64
- if ($mode == 1) {
- $line = rtrim($line, "\t\r\n\0\x0B");
- // create chunks with proper length for base64 decoding
- $line = $prev.$line;
- $length = strlen($line);
- if ($length % 4) {
- $length = floor($length / 4) * 4;
- $prev = substr($line, $length);
- $line = substr($line, 0, $length);
- }
- else
- $prev = '';
- $line = base64_decode($line);
- // QUOTED-PRINTABLE
- } else if ($mode == 2) {
- $line = rtrim($line, "\t\r\0\x0B");
- $line = quoted_printable_decode($line);
- // UUENCODE
- } else if ($mode == 3) {
- $line = rtrim($line, "\t\r\n\0\x0B");
- if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line))
- continue;
- $line = convert_uudecode($line);
- // default
- } else if ($formatted) {
- $line = rtrim($line, "\t\r\n\0\x0B") . "\n";
- }
-
- if ($file)
- fwrite($file, $line);
- else if ($print)
- echo $line;
- else
- $result .= $line;
- }
- }
-
- // read in anything up until last line
- if (!$end)
- do {
- $line = $this->readLine(1024);
- } while (!$this->startsWith($line, $key, true));
-
- if ($result !== false) {
- if ($file) {
- fwrite($file, $result);
- } else if ($print) {
- echo $result;
- } else
- return $result;
- return true;
- }
-
- return false;
- }
-
- /**
- * Handler for IMAP APPEND command
- *
- * @param string $mailbox Mailbox name
- * @param string $message Message content
- * @param array $flags Message flags
- * @param string $date Message internal date
- *
- * @return string|bool On success APPENDUID response (if available) or True, False on failure
- */
- function append($mailbox, &$message, $flags = array(), $date = null)
- {
- unset($this->data['APPENDUID']);
-
- if ($mailbox === null || $mailbox === '') {
- return false;
- }
-
- $message = str_replace("\r", '', $message);
- $message = str_replace("\n", "\r\n", $message);
-
- $len = strlen($message);
- if (!$len) {
- return false;
- }
-
- // build APPEND command
- $key = $this->nextTag();
- $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
- if (!empty($date)) {
- $request .= ' ' . $this->escape($date);
- }
- $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
-
- // send APPEND command
- if ($this->putLine($request)) {
- // Do not wait when LITERAL+ is supported
- if (!$this->prefs['literal+']) {
- $line = $this->readReply();
-
- if ($line[0] != '+') {
- $this->parseResult($line, 'APPEND: ');
- return false;
- }
- }
-
- if (!$this->putLine($message)) {
- return false;
- }
-
- do {
- $line = $this->readLine();
- } while (!$this->startsWith($line, $key, true, true));
-
- // Clear internal status cache
- unset($this->data['STATUS:'.$mailbox]);
-
- if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK)
- return false;
- else if (!empty($this->data['APPENDUID']))
- return $this->data['APPENDUID'];
- else
- return true;
- }
- else {
- $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
- }
-
- return false;
- }
-
- /**
- * Handler for IMAP APPEND command.
- *
- * @param string $mailbox Mailbox name
- * @param string $path Path to the file with message body
- * @param string $headers Message headers
- * @param array $flags Message flags
- * @param string $date Message internal date
- *
- * @return string|bool On success APPENDUID response (if available) or True, False on failure
- */
- function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null)
- {
- unset($this->data['APPENDUID']);
-
- if ($mailbox === null || $mailbox === '') {
- return false;
- }
-
- // open message file
- $in_fp = false;
- if (file_exists(realpath($path))) {
- $in_fp = fopen($path, 'r');
- }
-
- if (!$in_fp) {
- $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
- return false;
- }
-
- $body_separator = "\r\n\r\n";
- $len = filesize($path);
-
- if (!$len) {
- return false;
- }
-
- if ($headers) {
- $headers = preg_replace('/[\r\n]+$/', '', $headers);
- $len += strlen($headers) + strlen($body_separator);
- }
-
- // build APPEND command
- $key = $this->nextTag();
- $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
- if (!empty($date)) {
- $request .= ' ' . $this->escape($date);
- }
- $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
-
- // send APPEND command
- if ($this->putLine($request)) {
- // Don't wait when LITERAL+ is supported
- if (!$this->prefs['literal+']) {
- $line = $this->readReply();
-
- if ($line[0] != '+') {
- $this->parseResult($line, 'APPEND: ');
- return false;
- }
- }
-
- // send headers with body separator
- if ($headers) {
- $this->putLine($headers . $body_separator, false);
- }
-
- // send file
- while (!feof($in_fp) && $this->fp) {
- $buffer = fgets($in_fp, 4096);
- $this->putLine($buffer, false);
- }
- fclose($in_fp);
-
- if (!$this->putLine('')) { // \r\n
- return false;
- }
-
- // read response
- do {
- $line = $this->readLine();
- } while (!$this->startsWith($line, $key, true, true));
-
- // Clear internal status cache
- unset($this->data['STATUS:'.$mailbox]);
-
- if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK)
- return false;
- else if (!empty($this->data['APPENDUID']))
- return $this->data['APPENDUID'];
- else
- return true;
- }
- else {
- $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
- }
-
- return false;
- }
-
- /**
- * Returns QUOTA information
- *
- * @return array Quota information
- */
- function getQuota()
- {
- /*
- * GETQUOTAROOT "INBOX"
- * QUOTAROOT INBOX user/rchijiiwa1
- * QUOTA user/rchijiiwa1 (STORAGE 654 9765)
- * OK Completed
- */
- $result = false;
- $quota_lines = array();
- $key = $this->nextTag();
- $command = $key . ' GETQUOTAROOT INBOX';
-
- // get line(s) containing quota info
- if ($this->putLine($command)) {
- do {
- $line = rtrim($this->readLine(5000));
- if (preg_match('/^\* QUOTA /', $line)) {
- $quota_lines[] = $line;
- }
- } while (!$this->startsWith($line, $key, true, true));
- }
- else {
- $this->setError(self::ERROR_COMMAND, "Unable to send command: $command");
- }
-
- // return false if not found, parse if found
- $min_free = PHP_INT_MAX;
- foreach ($quota_lines as $key => $quota_line) {
- $quota_line = str_replace(array('(', ')'), '', $quota_line);
- $parts = explode(' ', $quota_line);
- $storage_part = array_search('STORAGE', $parts);
-
- if (!$storage_part) {
- continue;
- }
-
- $used = intval($parts[$storage_part+1]);
- $total = intval($parts[$storage_part+2]);
- $free = $total - $used;
-
- // return lowest available space from all quotas
- if ($free < $min_free) {
- $min_free = $free;
- $result['used'] = $used;
- $result['total'] = $total;
- $result['percent'] = min(100, round(($used/max(1,$total))*100));
- $result['free'] = 100 - $result['percent'];
- }
- }
-
- return $result;
- }
-
- /**
- * Send the SETACL command (RFC4314)
- *
- * @param string $mailbox Mailbox name
- * @param string $user User name
- * @param mixed $acl ACL string or array
- *
- * @return boolean True on success, False on failure
- *
- * @since 0.5-beta
- */
- function setACL($mailbox, $user, $acl)
- {
- if (is_array($acl)) {
- $acl = implode('', $acl);
- }
-
- $result = $this->execute('SETACL', array(
- $this->escape($mailbox), $this->escape($user), strtolower($acl)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Send the DELETEACL command (RFC4314)
- *
- * @param string $mailbox Mailbox name
- * @param string $user User name
- *
- * @return boolean True on success, False on failure
- *
- * @since 0.5-beta
- */
- function deleteACL($mailbox, $user)
- {
- $result = $this->execute('DELETEACL', array(
- $this->escape($mailbox), $this->escape($user)),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Send the GETACL command (RFC4314)
- *
- * @param string $mailbox Mailbox name
- *
- * @return array User-rights array on success, NULL on error
- * @since 0.5-beta
- */
- function getACL($mailbox)
- {
- list($code, $response) = $this->execute('GETACL', array($this->escape($mailbox)));
-
- if ($code == self::ERROR_OK && preg_match('/^\* ACL /i', $response)) {
- // Parse server response (remove "* ACL ")
- $response = substr($response, 6);
- $ret = $this->tokenizeResponse($response);
- $mbox = array_shift($ret);
- $size = count($ret);
-
- // Create user-rights hash array
- // @TODO: consider implementing fixACL() method according to RFC4314.2.1.1
- // so we could return only standard rights defined in RFC4314,
- // excluding 'c' and 'd' defined in RFC2086.
- if ($size % 2 == 0) {
- for ($i=0; $i<$size; $i++) {
- $ret[$ret[$i]] = str_split($ret[++$i]);
- unset($ret[$i-1]);
- unset($ret[$i]);
- }
- return $ret;
- }
-
- $this->setError(self::ERROR_COMMAND, "Incomplete ACL response");
- return NULL;
- }
-
- return NULL;
- }
-
- /**
- * Send the LISTRIGHTS command (RFC4314)
- *
- * @param string $mailbox Mailbox name
- * @param string $user User name
- *
- * @return array List of user rights
- * @since 0.5-beta
- */
- function listRights($mailbox, $user)
- {
- list($code, $response) = $this->execute('LISTRIGHTS', array(
- $this->escape($mailbox), $this->escape($user)));
-
- if ($code == self::ERROR_OK && preg_match('/^\* LISTRIGHTS /i', $response)) {
- // Parse server response (remove "* LISTRIGHTS ")
- $response = substr($response, 13);
-
- $ret_mbox = $this->tokenizeResponse($response, 1);
- $ret_user = $this->tokenizeResponse($response, 1);
- $granted = $this->tokenizeResponse($response, 1);
- $optional = trim($response);
-
- return array(
- 'granted' => str_split($granted),
- 'optional' => explode(' ', $optional),
- );
- }
-
- return NULL;
- }
-
- /**
- * Send the MYRIGHTS command (RFC4314)
- *
- * @param string $mailbox Mailbox name
- *
- * @return array MYRIGHTS response on success, NULL on error
- * @since 0.5-beta
- */
- function myRights($mailbox)
- {
- list($code, $response) = $this->execute('MYRIGHTS', array($this->escape($mailbox)));
-
- if ($code == self::ERROR_OK && preg_match('/^\* MYRIGHTS /i', $response)) {
- // Parse server response (remove "* MYRIGHTS ")
- $response = substr($response, 11);
-
- $ret_mbox = $this->tokenizeResponse($response, 1);
- $rights = $this->tokenizeResponse($response, 1);
-
- return str_split($rights);
- }
-
- return NULL;
- }
-
- /**
- * Send the SETMETADATA command (RFC5464)
- *
- * @param string $mailbox Mailbox name
- * @param array $entries Entry-value array (use NULL value as NIL)
- *
- * @return boolean True on success, False on failure
- * @since 0.5-beta
- */
- function setMetadata($mailbox, $entries)
- {
- if (!is_array($entries) || empty($entries)) {
- $this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");
- return false;
- }
-
- foreach ($entries as $name => $value) {
- $entries[$name] = $this->escape($name) . ' ' . $this->escape($value);
- }
-
- $entries = implode(' ', $entries);
- $result = $this->execute('SETMETADATA', array(
- $this->escape($mailbox), '(' . $entries . ')'),
- self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Send the SETMETADATA command with NIL values (RFC5464)
- *
- * @param string $mailbox Mailbox name
- * @param array $entries Entry names array
- *
- * @return boolean True on success, False on failure
- *
- * @since 0.5-beta
- */
- function deleteMetadata($mailbox, $entries)
- {
- if (!is_array($entries) && !empty($entries)) {
- $entries = explode(' ', $entries);
- }
-
- if (empty($entries)) {
- $this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");
- return false;
- }
-
- foreach ($entries as $entry) {
- $data[$entry] = NULL;
- }
-
- return $this->setMetadata($mailbox, $data);
- }
-
- /**
- * Send the GETMETADATA command (RFC5464)
- *
- * @param string $mailbox Mailbox name
- * @param array $entries Entries
- * @param array $options Command options (with MAXSIZE and DEPTH keys)
- *
- * @return array GETMETADATA result on success, NULL on error
- *
- * @since 0.5-beta
- */
- function getMetadata($mailbox, $entries, $options=array())
- {
- if (!is_array($entries)) {
- $entries = array($entries);
- }
-
- // create entries string
- foreach ($entries as $idx => $name) {
- $entries[$idx] = $this->escape($name);
- }
-
- $optlist = '';
- $entlist = '(' . implode(' ', $entries) . ')';
-
- // create options string
- if (is_array($options)) {
- $options = array_change_key_case($options, CASE_UPPER);
- $opts = array();
-
- if (!empty($options['MAXSIZE'])) {
- $opts[] = 'MAXSIZE '.intval($options['MAXSIZE']);
- }
- if (!empty($options['DEPTH'])) {
- $opts[] = 'DEPTH '.intval($options['DEPTH']);
- }
-
- if ($opts) {
- $optlist = '(' . implode(' ', $opts) . ')';
- }
- }
-
- $optlist .= ($optlist ? ' ' : '') . $entlist;
-
- list($code, $response) = $this->execute('GETMETADATA', array(
- $this->escape($mailbox), $optlist));
-
- if ($code == self::ERROR_OK) {
- $result = array();
- $data = $this->tokenizeResponse($response);
-
- // The METADATA response can contain multiple entries in a single
- // response or multiple responses for each entry or group of entries
- if (!empty($data) && ($size = count($data))) {
- for ($i=0; $i<$size; $i++) {
- if (isset($mbox) && is_array($data[$i])) {
- $size_sub = count($data[$i]);
- for ($x=0; $x<$size_sub; $x++) {
- $result[$mbox][$data[$i][$x]] = $data[$i][++$x];
- }
- unset($data[$i]);
- }
- else if ($data[$i] == '*') {
- if ($data[$i+1] == 'METADATA') {
- $mbox = $data[$i+2];
- unset($data[$i]); // "*"
- unset($data[++$i]); // "METADATA"
- unset($data[++$i]); // Mailbox
- }
- // get rid of other untagged responses
- else {
- unset($mbox);
- unset($data[$i]);
- }
- }
- else if (isset($mbox)) {
- $result[$mbox][$data[$i]] = $data[++$i];
- unset($data[$i]);
- unset($data[$i-1]);
- }
- else {
- unset($data[$i]);
- }
- }
- }
-
- return $result;
- }
-
- return NULL;
- }
-
- /**
- * Send the SETANNOTATION command (draft-daboo-imap-annotatemore)
- *
- * @param string $mailbox Mailbox name
- * @param array $data Data array where each item is an array with
- * three elements: entry name, attribute name, value
- *
- * @return boolean True on success, False on failure
- * @since 0.5-beta
- */
- function setAnnotation($mailbox, $data)
- {
- if (!is_array($data) || empty($data)) {
- $this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command");
- return false;
- }
-
- foreach ($data as $entry) {
- // ANNOTATEMORE drafts before version 08 require quoted parameters
- $entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true),
- $this->escape($entry[1], true), $this->escape($entry[2], true));
- }
-
- $entries = implode(' ', $entries);
- $result = $this->execute('SETANNOTATION', array(
- $this->escape($mailbox), $entries), self::COMMAND_NORESPONSE);
-
- return ($result == self::ERROR_OK);
- }
-
- /**
- * Send the SETANNOTATION command with NIL values (draft-daboo-imap-annotatemore)
- *
- * @param string $mailbox Mailbox name
- * @param array $data Data array where each item is an array with
- * two elements: entry name and attribute name
- *
- * @return boolean True on success, False on failure
- *
- * @since 0.5-beta
- */
- function deleteAnnotation($mailbox, $data)
- {
- if (!is_array($data) || empty($data)) {
- $this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command");
- return false;
- }
-
- return $this->setAnnotation($mailbox, $data);
- }
-
- /**
- * Send the GETANNOTATION command (draft-daboo-imap-annotatemore)
- *
- * @param string $mailbox Mailbox name
- * @param array $entries Entries names
- * @param array $attribs Attribs names
- *
- * @return array Annotations result on success, NULL on error
- *
- * @since 0.5-beta
- */
- function getAnnotation($mailbox, $entries, $attribs)
- {
- if (!is_array($entries)) {
- $entries = array($entries);
- }
- // create entries string
- // ANNOTATEMORE drafts before version 08 require quoted parameters
- foreach ($entries as $idx => $name) {
- $entries[$idx] = $this->escape($name, true);
- }
- $entries = '(' . implode(' ', $entries) . ')';
-
- if (!is_array($attribs)) {
- $attribs = array($attribs);
- }
- // create entries string
- foreach ($attribs as $idx => $name) {
- $attribs[$idx] = $this->escape($name, true);
- }
- $attribs = '(' . implode(' ', $attribs) . ')';
-
- list($code, $response) = $this->execute('GETANNOTATION', array(
- $this->escape($mailbox), $entries, $attribs));
-
- if ($code == self::ERROR_OK) {
- $result = array();
- $data = $this->tokenizeResponse($response);
-
- // Here we returns only data compatible with METADATA result format
- if (!empty($data) && ($size = count($data))) {
- for ($i=0; $i<$size; $i++) {
- $entry = $data[$i];
- if (isset($mbox) && is_array($entry)) {
- $attribs = $entry;
- $entry = $last_entry;
- }
- else if ($entry == '*') {
- if ($data[$i+1] == 'ANNOTATION') {
- $mbox = $data[$i+2];
- unset($data[$i]); // "*"
- unset($data[++$i]); // "ANNOTATION"
- unset($data[++$i]); // Mailbox
- }
- // get rid of other untagged responses
- else {
- unset($mbox);
- unset($data[$i]);
- }
- continue;
- }
- else if (isset($mbox)) {
- $attribs = $data[++$i];
- }
- else {
- unset($data[$i]);
- continue;
- }
-
- if (!empty($attribs)) {
- for ($x=0, $len=count($attribs); $x<$len;) {
- $attr = $attribs[$x++];
- $value = $attribs[$x++];
- if ($attr == 'value.priv') {
- $result[$mbox]['/private' . $entry] = $value;
- }
- else if ($attr == 'value.shared') {
- $result[$mbox]['/shared' . $entry] = $value;
- }
- }
- }
- $last_entry = $entry;
- unset($data[$i]);
- }
- }
-
- return $result;
- }
-
- return NULL;
- }
-
- /**
- * Returns BODYSTRUCTURE for the specified message.
- *
- * @param string $mailbox Folder name
- * @param int $id Message sequence number or UID
- * @param bool $is_uid True if $id is an UID
- *
- * @return array/bool Body structure array or False on error.
- * @since 0.6
- */
- function getStructure($mailbox, $id, $is_uid = false)
- {
- $result = $this->fetch($mailbox, $id, $is_uid, array('BODYSTRUCTURE'));
- if (is_array($result)) {
- $result = array_shift($result);
- return $result->bodystructure;
- }
- return false;
- }
-
- /**
- * Returns data of a message part according to specified structure.
- *
- * @param array $structure Message structure (getStructure() result)
- * @param string $part Message part identifier
- *
- * @return array Part data as hash array (type, encoding, charset, size)
- */
- static function getStructurePartData($structure, $part)
- {
- $part_a = self::getStructurePartArray($structure, $part);
- $data = array();
-
- if (empty($part_a)) {
- return $data;
- }
-
- // content-type
- if (is_array($part_a[0])) {
- $data['type'] = 'multipart';
- }
- else {
- $data['type'] = strtolower($part_a[0]);
-
- // encoding
- $data['encoding'] = strtolower($part_a[5]);
-
- // charset
- if (is_array($part_a[2])) {
- while (list($key, $val) = each($part_a[2])) {
- if (strcasecmp($val, 'charset') == 0) {
- $data['charset'] = $part_a[2][$key+1];
- break;
- }
- }
- }
- }
-
- // size
- $data['size'] = intval($part_a[6]);
-
- return $data;
- }
-
- static function getStructurePartArray($a, $part)
- {
- if (!is_array($a)) {
- return false;
- }
-
- if (empty($part)) {
- return $a;
- }
-
- $ctype = is_string($a[0]) && is_string($a[1]) ? $a[0] . '/' . $a[1] : '';
-
- if (strcasecmp($ctype, 'message/rfc822') == 0) {
- $a = $a[8];
- }
-
- if (strpos($part, '.') > 0) {
- $orig_part = $part;
- $pos = strpos($part, '.');
- $rest = substr($orig_part, $pos+1);
- $part = substr($orig_part, 0, $pos);
-
- return self::getStructurePartArray($a[$part-1], $rest);
- }
- else if ($part > 0) {
- return (is_array($a[$part-1])) ? $a[$part-1] : $a;
- }
- }
-
- /**
- * Creates next command identifier (tag)
- *
- * @return string Command identifier
- * @since 0.5-beta
- */
- function nextTag()
- {
- $this->cmd_num++;
- $this->cmd_tag = sprintf('A%04d', $this->cmd_num);
-
- return $this->cmd_tag;
- }
-
- /**
- * Sends IMAP command and parses result
- *
- * @param string $command IMAP command
- * @param array $arguments Command arguments
- * @param int $options Execution options
- *
- * @return mixed Response code or list of response code and data
- * @since 0.5-beta
- */
- function execute($command, $arguments=array(), $options=0)
- {
- $tag = $this->nextTag();
- $query = $tag . ' ' . $command;
- $noresp = ($options & self::COMMAND_NORESPONSE);
- $response = $noresp ? null : '';
-
- if (!empty($arguments)) {
- foreach ($arguments as $arg) {
- $query .= ' ' . self::r_implode($arg);
- }
- }
-
- // Send command
- if (!$this->putLineC($query)) {
- $this->setError(self::ERROR_COMMAND, "Unable to send command: $query");
- return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, '');
- }
-
- // Parse response
- do {
- $line = $this->readLine(4096);
- if ($response !== null) {
- $response .= $line;
- }
- } while (!$this->startsWith($line, $tag . ' ', true, true));
-
- $code = $this->parseResult($line, $command . ': ');
-
- // Remove last line from response
- if ($response) {
- $line_len = min(strlen($response), strlen($line) + 2);
- $response = substr($response, 0, -$line_len);
- }
-
- // optional CAPABILITY response
- if (($options & self::COMMAND_CAPABILITY) && $code == self::ERROR_OK
- && preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)
- ) {
- $this->parseCapability($matches[1], true);
- }
-
- // return last line only (without command tag, result and response code)
- if ($line && ($options & self::COMMAND_LASTLINE)) {
- $response = preg_replace("/^$tag (OK|NO|BAD|BYE|PREAUTH)?\s*(\[[a-z-]+\])?\s*/i", '', trim($line));
- }
-
- return $noresp ? $code : array($code, $response);
- }
-
- /**
- * Splits IMAP response into string tokens
- *
- * @param string &$str The IMAP's server response
- * @param int $num Number of tokens to return
- *
- * @return mixed Tokens array or string if $num=1
- * @since 0.5-beta
- */
- static function tokenizeResponse(&$str, $num=0)
- {
- $result = array();
-
- while (!$num || count($result) < $num) {
- // remove spaces from the beginning of the string
- $str = ltrim($str);
-
- switch ($str[0]) {
-
- // String literal
- case '{':
- if (($epos = strpos($str, "}\r\n", 1)) == false) {
- // error
- }
- if (!is_numeric(($bytes = substr($str, 1, $epos - 1)))) {
- // error
- }
- $result[] = $bytes ? substr($str, $epos + 3, $bytes) : '';
- // Advance the string
- $str = substr($str, $epos + 3 + $bytes);
- break;
-
- // Quoted string
- case '"':
- $len = strlen($str);
-
- for ($pos=1; $pos<$len; $pos++) {
- if ($str[$pos] == '"') {
- break;
- }
- if ($str[$pos] == "\\") {
- if ($str[$pos + 1] == '"' || $str[$pos + 1] == "\\") {
- $pos++;
- }
- }
- }
- if ($str[$pos] != '"') {
- // error
- }
- // we need to strip slashes for a quoted string
- $result[] = stripslashes(substr($str, 1, $pos - 1));
- $str = substr($str, $pos + 1);
- break;
-
- // Parenthesized list
- case '(':
- case '[':
- $str = substr($str, 1);
- $result[] = self::tokenizeResponse($str);
- break;
- case ')':
- case ']':
- $str = substr($str, 1);
- return $result;
- break;
-
- // String atom, number, NIL, *, %
- default:
- // empty string
- if ($str === '' || $str === null) {
- break 2;
- }
-
- // excluded chars: SP, CTL, ), [, ]
- if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) {
- $result[] = $m[1] == 'NIL' ? NULL : $m[1];
- $str = substr($str, strlen($m[1]));
- }
- break;
- }
- }
-
- return $num == 1 ? $result[0] : $result;
- }
-
- static function r_implode($element)
- {
- $string = '';
-
- if (is_array($element)) {
- reset($element);
- while (list($key, $value) = each($element)) {
- $string .= ' ' . self::r_implode($value);
- }
- }
- else {
- return $element;
- }
-
- return '(' . trim($string) . ')';
- }
-
- /**
- * Converts message identifiers array into sequence-set syntax
- *
- * @param array $messages Message identifiers
- * @param bool $force Forces compression of any size
- *
- * @return string Compressed sequence-set
- */
- static function compressMessageSet($messages, $force=false)
- {
- // given a comma delimited list of independent mid's,
- // compresses by grouping sequences together
-
- if (!is_array($messages)) {
- // if less than 255 bytes long, let's not bother
- if (!$force && strlen($messages)<255) {
- return $messages;
- }
-
- // see if it's already been compressed
- if (strpos($messages, ':') !== false) {
- return $messages;
- }
-
- // separate, then sort
- $messages = explode(',', $messages);
- }
-
- sort($messages);
-
- $result = array();
- $start = $prev = $messages[0];
-
- foreach ($messages as $id) {
- $incr = $id - $prev;
- if ($incr > 1) { // found a gap
- if ($start == $prev) {
- $result[] = $prev; // push single id
- } else {
- $result[] = $start . ':' . $prev; // push sequence as start_id:end_id
- }
- $start = $id; // start of new sequence
- }
- $prev = $id;
- }
-
- // handle the last sequence/id
- if ($start == $prev) {
- $result[] = $prev;
- } else {
- $result[] = $start.':'.$prev;
- }
-
- // return as comma separated string
- return implode(',', $result);
- }
-
- /**
- * Converts message sequence-set into array
- *
- * @param string $messages Message identifiers
- *
- * @return array List of message identifiers
- */
- static function uncompressMessageSet($messages)
- {
- $result = array();
- $messages = explode(',', $messages);
-
- foreach ($messages as $idx => $part) {
- $items = explode(':', $part);
- $max = max($items[0], $items[1]);
-
- for ($x=$items[0]; $x<=$max; $x++) {
- $result[] = $x;
- }
- unset($messages[$idx]);
- }
-
- return $result;
- }
-
- private function _xor($string, $string2)
- {
- $result = '';
- $size = strlen($string);
-
- for ($i=0; $i<$size; $i++) {
- $result .= chr(ord($string[$i]) ^ ord($string2[$i]));
- }
-
- return $result;
- }
-
- /**
- * Converts flags array into string for inclusion in IMAP command
- *
- * @param array $flags Flags (see self::flags)
- *
- * @return string Space-separated list of flags
- */
- private function flagsToStr($flags)
- {
- foreach ((array)$flags as $idx => $flag) {
- if ($flag = $this->flags[strtoupper($flag)]) {
- $flags[$idx] = $flag;
- }
- }
-
- return implode(' ', (array)$flags);
- }
-
- /**
- * Converts datetime string into unix timestamp
- *
- * @param string $date Date string
- *
- * @return int Unix timestamp
- */
- static function strToTime($date)
- {
- // support non-standard "GMTXXXX" literal
- $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
-
- // if date parsing fails, we have a date in non-rfc format
- // remove token from the end and try again
- while (($ts = intval(@strtotime($date))) <= 0) {
- $d = explode(' ', $date);
- array_pop($d);
- if (empty($d)) {
- break;
- }
- $date = implode(' ', $d);
- }
-
- return $ts < 0 ? 0 : $ts;
- }
-
- /**
- * CAPABILITY response parser
- */
- private function parseCapability($str, $trusted=false)
- {
- $str = preg_replace('/^\* CAPABILITY /i', '', $str);
-
- $this->capability = explode(' ', strtoupper($str));
-
- if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) {
- $this->prefs['literal+'] = true;
- }
-
- if ($trusted) {
- $this->capability_readed = true;
- }
- }
-
- /**
- * Escapes a string when it contains special characters (RFC3501)
- *
- * @param string $string IMAP string
- * @param boolean $force_quotes Forces string quoting (for atoms)
- *
- * @return string String atom, quoted-string or string literal
- * @todo lists
- */
- static function escape($string, $force_quotes=false)
- {
- if ($string === null) {
- return 'NIL';
- }
-
- if ($string === '') {
- return '""';
- }
-
- // atom-string (only safe characters)
- if (!$force_quotes && !preg_match('/[\x00-\x20\x22\x25\x28-\x2A\x5B-\x5D\x7B\x7D\x80-\xFF]/', $string)) {
- return $string;
- }
-
- // quoted-string
- if (!preg_match('/[\r\n\x00\x80-\xFF]/', $string)) {
- return '"' . addcslashes($string, '\\"') . '"';
- }
-
- // literal-string
- return sprintf("{%d}\r\n%s", strlen($string), $string);
- }
-
- /**
- * Unescapes quoted-string
- *
- * @param string $string IMAP string
- *
- * @return string String
- */
- static function unEscape($string)
- {
- return stripslashes($string);
- }
-
- /**
- * Set the value of the debugging flag.
- *
- * @param boolean $debug New value for the debugging flag.
- *
- * @since 0.5-stable
- */
- function setDebug($debug, $handler = null)
- {
- $this->_debug = $debug;
- $this->_debug_handler = $handler;
- }
-
- /**
- * Write the given debug text to the current debug output handler.
- *
- * @param string $message Debug mesage text.
- *
- * @since 0.5-stable
- */
- private function debug($message)
- {
- if ($this->resourceid) {
- $message = sprintf('[%s] %s', $this->resourceid, $message);
- }
-
- if ($this->_debug_handler) {
- call_user_func_array($this->_debug_handler, array(&$this, $message));
- } else {
- echo "DEBUG: $message\n";
- }
- }
-
-}
diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php
deleted file mode 100644
index 61a073fa3..000000000
--- a/program/include/rcube_ldap.php
+++ /dev/null
@@ -1,2323 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_ldap.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2012, The Roundcube Dev Team |
- | Copyright (C) 2011, 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: |
- | Interface to an LDAP address directory |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- | Andreas Dick <andudi (at) gmx (dot) ch> |
- | Aleksander Machniak <machniak@kolabsys.com> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Model class to access an LDAP address directory
- *
- * @package Addressbook
- */
-class rcube_ldap extends rcube_addressbook
-{
- /** public properties */
- public $primary_key = 'ID';
- public $groups = false;
- public $readonly = true;
- public $ready = false;
- public $group_id = 0;
- public $coltypes = array();
-
- /** private properties */
- protected $conn;
- protected $prop = array();
- protected $fieldmap = array();
- protected $sub_filter;
- protected $filter = '';
- protected $result = null;
- protected $ldap_result = null;
- protected $mail_domain = '';
- protected $debug = false;
-
- private $base_dn = '';
- private $groups_base_dn = '';
- private $group_url = null;
- private $cache;
-
- private $vlv_active = false;
- private $vlv_count = 0;
-
-
- /**
- * Object constructor
- *
- * @param array $p LDAP connection properties
- * @param boolean $debug Enables debug mode
- * @param string $mail_domain Current user mail domain name
- */
- function __construct($p, $debug = false, $mail_domain = null)
- {
- $this->prop = $p;
-
- if (isset($p['searchonly']))
- $this->searchonly = $p['searchonly'];
-
- // check if groups are configured
- if (is_array($p['groups']) && count($p['groups'])) {
- $this->groups = true;
- // set member field
- if (!empty($p['groups']['member_attr']))
- $this->prop['member_attr'] = strtolower($p['groups']['member_attr']);
- else if (empty($p['member_attr']))
- $this->prop['member_attr'] = 'member';
- // set default name attribute to cn
- if (empty($this->prop['groups']['name_attr']))
- $this->prop['groups']['name_attr'] = 'cn';
- if (empty($this->prop['groups']['scope']))
- $this->prop['groups']['scope'] = 'sub';
- }
-
- // fieldmap property is given
- if (is_array($p['fieldmap'])) {
- foreach ($p['fieldmap'] as $rf => $lf)
- $this->fieldmap[$rf] = $this->_attr_name(strtolower($lf));
- }
- else if (!empty($p)) {
- // read deprecated *_field properties to remain backwards compatible
- foreach ($p as $prop => $value)
- if (preg_match('/^(.+)_field$/', $prop, $matches))
- $this->fieldmap[$matches[1]] = $this->_attr_name(strtolower($value));
- }
-
- // use fieldmap to advertise supported coltypes to the application
- foreach ($this->fieldmap as $colv => $lfv) {
- list($col, $type) = explode(':', $colv);
- list($lf, $limit, $delim) = explode(':', $lfv);
-
- if ($limit == '*') $limit = null;
- else $limit = max(1, intval($limit));
-
- if (!is_array($this->coltypes[$col])) {
- $subtypes = $type ? array($type) : null;
- $this->coltypes[$col] = array('limit' => $limit, 'subtypes' => $subtypes, 'attributes' => array($lf));
- }
- elseif ($type) {
- $this->coltypes[$col]['subtypes'][] = $type;
- $this->coltypes[$col]['attributes'][] = $lf;
- $this->coltypes[$col]['limit'] += $limit;
- }
-
- if ($delim)
- $this->coltypes[$col]['serialized'][$type] = $delim;
-
- $this->fieldmap[$colv] = $lf;
- }
-
- // support for composite address
- if ($this->coltypes['street'] && $this->coltypes['locality']) {
- $this->coltypes['address'] = array(
- 'limit' => max(1, $this->coltypes['locality']['limit'] + $this->coltypes['address']['limit']),
- 'subtypes' => array_merge((array)$this->coltypes['address']['subtypes'], (array)$this->coltypes['locality']['subtypes']),
- 'childs' => array(),
- ) + (array)$this->coltypes['address'];
-
- foreach (array('street','locality','zipcode','region','country') as $childcol) {
- if ($this->coltypes[$childcol]) {
- $this->coltypes['address']['childs'][$childcol] = array('type' => 'text');
- unset($this->coltypes[$childcol]); // remove address child col from global coltypes list
- }
- }
-
- // at least one address type must be specified
- if (empty($this->coltypes['address']['subtypes'])) {
- $this->coltypes['address']['subtypes'] = array('home');
- }
- }
- else if ($this->coltypes['address']) {
- $this->coltypes['address'] += array('type' => 'textarea', 'childs' => null, 'size' => 40);
-
- // 'serialized' means the UI has to present a composite address field
- if ($this->coltypes['address']['serialized']) {
- $childprop = array('type' => 'text');
- $this->coltypes['address']['type'] = 'composite';
- $this->coltypes['address']['childs'] = array('street' => $childprop, 'locality' => $childprop, 'zipcode' => $childprop, 'country' => $childprop);
- }
- }
-
- // make sure 'required_fields' is an array
- if (!is_array($this->prop['required_fields'])) {
- $this->prop['required_fields'] = (array) $this->prop['required_fields'];
- }
-
- // make sure LDAP_rdn field is required
- if (!empty($this->prop['LDAP_rdn']) && !in_array($this->prop['LDAP_rdn'], $this->prop['required_fields'])) {
- $this->prop['required_fields'][] = $this->prop['LDAP_rdn'];
- }
-
- foreach ($this->prop['required_fields'] as $key => $val) {
- $this->prop['required_fields'][$key] = $this->_attr_name(strtolower($val));
- }
-
- // Build sub_fields filter
- if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) {
- $this->sub_filter = '';
- foreach ($this->prop['sub_fields'] as $attr => $class) {
- if (!empty($class)) {
- $class = is_array($class) ? array_pop($class) : $class;
- $this->sub_filter .= '(objectClass=' . $class . ')';
- }
- }
- if (count($this->prop['sub_fields']) > 1) {
- $this->sub_filter = '(|' . $this->sub_filter . ')';
- }
- }
-
- $this->sort_col = is_array($p['sort']) ? $p['sort'][0] : $p['sort'];
- $this->debug = $debug;
- $this->mail_domain = $mail_domain;
-
- // initialize cache
- $rcube = rcube::get_instance();
- $this->cache = $rcube->get_cache('LDAP.' . asciiwords($this->prop['name']), 'db', 600);
-
- $this->_connect();
- }
-
-
- /**
- * Establish a connection to the LDAP server
- */
- private function _connect()
- {
- $rcube = rcube::get_instance();
-
- if (!function_exists('ldap_connect'))
- rcube::raise_error(array('code' => 100, 'type' => 'ldap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "No ldap support in this installation of PHP"),
- true, true);
-
- if (is_resource($this->conn))
- return true;
-
- if (!is_array($this->prop['hosts']))
- $this->prop['hosts'] = array($this->prop['hosts']);
-
- if (empty($this->prop['ldap_version']))
- $this->prop['ldap_version'] = 3;
-
- foreach ($this->prop['hosts'] as $host)
- {
- $host = rcube_utils::idn_to_ascii(rcube_utils::parse_host($host));
- $hostname = $host.($this->prop['port'] ? ':'.$this->prop['port'] : '');
-
- $this->_debug("C: Connect [$hostname] [{$this->prop['name']}]");
-
- if ($lc = @ldap_connect($host, $this->prop['port']))
- {
- if ($this->prop['use_tls'] === true)
- if (!ldap_start_tls($lc))
- continue;
-
- $this->_debug("S: OK");
-
- ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['ldap_version']);
- $this->prop['host'] = $host;
- $this->conn = $lc;
-
- if (isset($this->prop['referrals']))
- ldap_set_option($lc, LDAP_OPT_REFERRALS, $this->prop['referrals']);
- break;
- }
- $this->_debug("S: NOT OK");
- }
-
- // See if the directory is writeable.
- if ($this->prop['writable']) {
- $this->readonly = false;
- }
-
- if (!is_resource($this->conn)) {
- rcube::raise_error(array('code' => 100, 'type' => 'ldap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not connect to any LDAP server, last tried $hostname"), true);
-
- return false;
- }
-
- $bind_pass = $this->prop['bind_pass'];
- $bind_user = $this->prop['bind_user'];
- $bind_dn = $this->prop['bind_dn'];
-
- $this->base_dn = $this->prop['base_dn'];
- $this->groups_base_dn = ($this->prop['groups']['base_dn']) ?
- $this->prop['groups']['base_dn'] : $this->base_dn;
-
- // User specific access, generate the proper values to use.
- if ($this->prop['user_specific']) {
- // No password set, use the session password
- if (empty($bind_pass)) {
- $bind_pass = $rcube->decrypt($_SESSION['password']);
- }
-
- // Get the pieces needed for variable replacement.
- if ($fu = $rcube->get_user_name())
- list($u, $d) = explode('@', $fu);
- else
- $d = $this->mail_domain;
-
- $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string
-
- $replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u);
-
- if ($this->prop['search_base_dn'] && $this->prop['search_filter']) {
- if (!empty($this->prop['search_bind_dn']) && !empty($this->prop['search_bind_pw'])) {
- $this->bind($this->prop['search_bind_dn'], $this->prop['search_bind_pw']);
- }
-
- // Search for the dn to use to authenticate
- $this->prop['search_base_dn'] = strtr($this->prop['search_base_dn'], $replaces);
- $this->prop['search_filter'] = strtr($this->prop['search_filter'], $replaces);
-
- $this->_debug("S: searching with base {$this->prop['search_base_dn']} for {$this->prop['search_filter']}");
-
- $res = @ldap_search($this->conn, $this->prop['search_base_dn'], $this->prop['search_filter'], array('uid'));
- if ($res) {
- if (($entry = ldap_first_entry($this->conn, $res))
- && ($bind_dn = ldap_get_dn($this->conn, $entry))
- ) {
- $this->_debug("S: search returned dn: $bind_dn");
- $dn = ldap_explode_dn($bind_dn, 1);
- $replaces['%dn'] = $dn[0];
- }
- }
- else {
- $this->_debug("S: ".ldap_error($this->conn));
- }
-
- // DN not found
- if (empty($replaces['%dn'])) {
- if (!empty($this->prop['search_dn_default']))
- $replaces['%dn'] = $this->prop['search_dn_default'];
- else {
- rcube::raise_error(array(
- 'code' => 100, 'type' => 'ldap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "DN not found using LDAP search."), true);
- return false;
- }
- }
- }
-
- // Replace the bind_dn and base_dn variables.
- $bind_dn = strtr($bind_dn, $replaces);
- $this->base_dn = strtr($this->base_dn, $replaces);
- $this->groups_base_dn = strtr($this->groups_base_dn, $replaces);
-
- if (empty($bind_user)) {
- $bind_user = $u;
- }
- }
-
- if (empty($bind_pass)) {
- $this->ready = true;
- }
- else {
- if (!empty($bind_dn)) {
- $this->ready = $this->bind($bind_dn, $bind_pass);
- }
- else if (!empty($this->prop['auth_cid'])) {
- $this->ready = $this->sasl_bind($this->prop['auth_cid'], $bind_pass, $bind_user);
- }
- else {
- $this->ready = $this->sasl_bind($bind_user, $bind_pass);
- }
- }
-
- return $this->ready;
- }
-
-
- /**
- * Bind connection with (SASL-) user and password
- *
- * @param string $authc Authentication user
- * @param string $pass Bind password
- * @param string $authz Autorization user
- *
- * @return boolean True on success, False on error
- */
- public function sasl_bind($authc, $pass, $authz=null)
- {
- if (!$this->conn) {
- return false;
- }
-
- if (!function_exists('ldap_sasl_bind')) {
- rcube::raise_error(array('code' => 100, 'type' => 'ldap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Unable to bind: ldap_sasl_bind() not exists"),
- true, true);
- }
-
- if (!empty($authz)) {
- $authz = 'u:' . $authz;
- }
-
- if (!empty($this->prop['auth_method'])) {
- $method = $this->prop['auth_method'];
- }
- else {
- $method = 'DIGEST-MD5';
- }
-
- $this->_debug("C: Bind [mech: $method, authc: $authc, authz: $authz] [pass: $pass]");
-
- if (ldap_sasl_bind($this->conn, NULL, $pass, $method, NULL, $authc, $authz)) {
- $this->_debug("S: OK");
- return true;
- }
-
- $this->_debug("S: ".ldap_error($this->conn));
-
- rcube::raise_error(array(
- 'code' => ldap_errno($this->conn), 'type' => 'ldap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Bind failed for authcid=$authc ".ldap_error($this->conn)),
- true);
-
- return false;
- }
-
-
- /**
- * Bind connection with DN and password
- *
- * @param string Bind DN
- * @param string Bind password
- *
- * @return boolean True on success, False on error
- */
- public function bind($dn, $pass)
- {
- if (!$this->conn) {
- return false;
- }
-
- $this->_debug("C: Bind [dn: $dn] [pass: $pass]");
-
- if (@ldap_bind($this->conn, $dn, $pass)) {
- $this->_debug("S: OK");
- return true;
- }
-
- $this->_debug("S: ".ldap_error($this->conn));
-
- rcube::raise_error(array(
- 'code' => ldap_errno($this->conn), 'type' => 'ldap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
- true);
-
- return false;
- }
-
-
- /**
- * Close connection to LDAP server
- */
- function close()
- {
- if ($this->conn)
- {
- $this->_debug("C: Close");
- ldap_unbind($this->conn);
- $this->conn = null;
- }
- }
-
-
- /**
- * Returns address book name
- *
- * @return string Address book name
- */
- function get_name()
- {
- return $this->prop['name'];
- }
-
-
- /**
- * 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 ($this->coltypes[$sort_col]['attributes'])
- $this->sort_col = $this->coltypes[$sort_col]['attributes'][0];
- }
-
-
- /**
- * Save a search string for future listings
- *
- * @param string $filter Filter string
- */
- function set_search_set($filter)
- {
- $this->filter = $filter;
- }
-
-
- /**
- * Getter for saved search properties
- *
- * @return mixed Search properties used by this class
- */
- function get_search_set()
- {
- return $this->filter;
- }
-
-
- /**
- * Reset all saved results and search parameters
- */
- function reset()
- {
- $this->result = null;
- $this->ldap_result = null;
- $this->filter = '';
- }
-
-
- /**
- * List the current set of contact records
- *
- * @param array List of cols to show
- * @param int Only return this number of records
- *
- * @return array Indexed list of contact records, each a hash array
- */
- function list_records($cols=null, $subset=0)
- {
- if ($this->prop['searchonly'] && empty($this->filter) && !$this->group_id)
- {
- $this->result = new rcube_result_set(0);
- $this->result->searchonly = true;
- return $this->result;
- }
-
- // fetch group members recursively
- if ($this->group_id && $this->group_data['dn'])
- {
- $entries = $this->list_group_members($this->group_data['dn']);
-
- // make list of entries unique and sort it
- $seen = array();
- foreach ($entries as $i => $rec) {
- if ($seen[$rec['dn']]++)
- unset($entries[$i]);
- }
- usort($entries, array($this, '_entry_sort_cmp'));
-
- $entries['count'] = count($entries);
- $this->result = new rcube_result_set($entries['count'], ($this->list_page-1) * $this->page_size);
- }
- else
- {
- // add general filter to query
- if (!empty($this->prop['filter']) && empty($this->filter))
- $this->set_search_set($this->prop['filter']);
-
- // exec LDAP search if no result resource is stored
- if ($this->conn && !$this->ldap_result)
- $this->_exec_search();
-
- // count contacts for this user
- $this->result = $this->count();
-
- // we have a search result resource
- if ($this->ldap_result && $this->result->count > 0)
- {
- // sorting still on the ldap server
- if ($this->sort_col && $this->prop['scope'] !== 'base' && !$this->vlv_active)
- ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
-
- // get all entries from the ldap server
- $entries = ldap_get_entries($this->conn, $this->ldap_result);
- }
-
- } // end else
-
- // start and end of the page
- $start_row = $this->vlv_active ? 0 : $this->result->first;
- $start_row = $subset < 0 ? $start_row + $this->page_size + $subset : $start_row;
- $last_row = $this->result->first + $this->page_size;
- $last_row = $subset != 0 ? $start_row + abs($subset) : $last_row;
-
- // filter entries for this page
- for ($i = $start_row; $i < min($entries['count'], $last_row); $i++)
- $this->result->add($this->_ldap2result($entries[$i]));
-
- return $this->result;
- }
-
- /**
- * Get all members of the given group
- *
- * @param string Group DN
- * @param array Group entries (if called recursively)
- * @return array Accumulated group members
- */
- function list_group_members($dn, $count = false, $entries = null)
- {
- $group_members = array();
-
- // fetch group object
- if (empty($entries)) {
- $result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL'));
- if ($result === false)
- {
- $this->_debug("S: ".ldap_error($this->conn));
- return $group_members;
- }
-
- $entries = @ldap_get_entries($this->conn, $result);
- }
-
- for ($i=0; $i < $entries['count']; $i++)
- {
- $entry = $entries[$i];
-
- if (empty($entry['objectclass']))
- continue;
-
- foreach ((array)$entry['objectclass'] as $objectclass)
- {
- switch (strtolower($objectclass)) {
- case "group":
- case "groupofnames":
- case "kolabgroupofnames":
- $group_members = array_merge($group_members, $this->_list_group_members($dn, $entry, 'member', $count));
- break;
- case "groupofuniquenames":
- case "kolabgroupofuniquenames":
- $group_members = array_merge($group_members, $this->_list_group_members($dn, $entry, 'uniquemember', $count));
- break;
- case "groupofurls":
- $group_members = array_merge($group_members, $this->_list_group_memberurl($dn, $entry, $count));
- break;
- }
- }
-
- if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit'])
- break;
- }
-
- return array_filter($group_members);
- }
-
- /**
- * Fetch members of the given group entry from server
- *
- * @param string Group DN
- * @param array Group entry
- * @param string Member attribute to use
- * @return array Accumulated group members
- */
- private function _list_group_members($dn, $entry, $attr, $count)
- {
- // Use the member attributes to return an array of member ldap objects
- // NOTE that the member attribute is supposed to contain a DN
- $group_members = array();
- if (empty($entry[$attr]))
- return $group_members;
-
- // read these attributes for all members
- $attrib = $count ? array('dn') : array_values($this->fieldmap);
- $attrib[] = 'objectClass';
- $attrib[] = 'member';
- $attrib[] = 'uniqueMember';
- $attrib[] = 'memberURL';
-
- for ($i=0; $i < $entry[$attr]['count']; $i++)
- {
- if (empty($entry[$attr][$i]))
- continue;
-
- $result = @ldap_read($this->conn, $entry[$attr][$i], '(objectclass=*)',
- $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']);
-
- $members = @ldap_get_entries($this->conn, $result);
- if ($members == false)
- {
- $this->_debug("S: ".ldap_error($this->conn));
- $members = array();
- }
-
- // for nested groups, call recursively
- $nested_group_members = $this->list_group_members($entry[$attr][$i], $count, $members);
-
- unset($members['count']);
- $group_members = array_merge($group_members, array_filter($members), $nested_group_members);
- }
-
- return $group_members;
- }
-
- /**
- * List members of group class groupOfUrls
- *
- * @param string Group DN
- * @param array Group entry
- * @param boolean True if only used for counting
- * @return array Accumulated group members
- */
- private function _list_group_memberurl($dn, $entry, $count)
- {
- $group_members = array();
-
- for ($i=0; $i < $entry['memberurl']['count']; $i++)
- {
- // extract components from url
- if (!preg_match('!ldap:///([^\?]+)\?\?(\w+)\?(.*)$!', $entry['memberurl'][$i], $m))
- continue;
-
- // add search filter if any
- $filter = $this->filter ? '(&(' . $m[3] . ')(' . $this->filter . '))' : $m[3];
- $func = $m[2] == 'sub' ? 'ldap_search' : ($m[2] == 'base' ? 'ldap_read' : 'ldap_list');
-
- $attrib = $count ? array('dn') : array_values($this->fieldmap);
- if ($result = @$func($this->conn, $m[1], $filter,
- $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
- ) {
- $this->_debug("S: ".ldap_count_entries($this->conn, $result)." record(s) for ".$m[1]);
- }
- else {
- $this->_debug("S: ".ldap_error($this->conn));
- return $group_members;
- }
-
- $entries = @ldap_get_entries($this->conn, $result);
- for ($j = 0; $j < $entries['count']; $j++)
- {
- if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))
- $group_members = array_merge($group_members, $nested_group_members);
- else
- $group_members[] = $entries[$j];
- }
- }
-
- return $group_members;
- }
-
- /**
- * Callback for sorting entries
- */
- function _entry_sort_cmp($a, $b)
- {
- return strcmp($a[$this->sort_col][0], $b[$this->sort_col][0]);
- }
-
-
- /**
- * Search contacts
- *
- * @param mixed $fields The field name of array of field names to search in
- * @param mixed $value Search value (or array of values when $fields is array)
- * @param int $mode Matching mode:
- * 0 - partial (*abc*),
- * 1 - strict (=),
- * 2 - prefix (abc*)
- * @param boolean $select True if results are requested, False if count only
- * @param boolean $nocount (Not used)
- * @param array $required List of fields that cannot be empty
- *
- * @return array Indexed list of contact records and 'count' value
- */
- function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array())
- {
- $mode = intval($mode);
-
- // special treatment for ID-based search
- if ($fields == 'ID' || $fields == $this->primary_key)
- {
- $ids = !is_array($value) ? explode(',', $value) : $value;
- $result = new rcube_result_set();
- foreach ($ids as $id)
- {
- if ($rec = $this->get_record($id, true))
- {
- $result->add($rec);
- $result->count++;
- }
- }
- return $result;
- }
-
- // use VLV pseudo-search for autocompletion
- $rcube = rcube::get_instance();
- $list_fields = $rcube->config->get('contactlist_fields');
-
- if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $list_fields))
- {
- // add general filter to query
- if (!empty($this->prop['filter']) && empty($this->filter))
- $this->set_search_set($this->prop['filter']);
-
- // set VLV controls with encoded search string
- $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size, $value);
-
- $function = $this->_scope2func($this->prop['scope']);
- $this->ldap_result = @$function($this->conn, $this->base_dn, $this->filter ? $this->filter : '(objectclass=*)',
- array_values($this->fieldmap), 0, $this->page_size, (int)$this->prop['timelimit']);
-
- $this->result = new rcube_result_set(0);
-
- if (!$this->ldap_result) {
- $this->_debug("S: ".ldap_error($this->conn));
- return $this->result;
- }
-
- $this->_debug("S: ".ldap_count_entries($this->conn, $this->ldap_result)." record(s)");
-
- // get all entries of this page and post-filter those that really match the query
- $search = mb_strtolower($value);
- $entries = ldap_get_entries($this->conn, $this->ldap_result);
-
- for ($i = 0; $i < $entries['count']; $i++) {
- $rec = $this->_ldap2result($entries[$i]);
- foreach ($fields as $f) {
- foreach ((array)$rec[$f] as $val) {
- $val = mb_strtolower($val);
- switch ($mode) {
- case 1:
- $got = ($val == $search);
- break;
- case 2:
- $got = ($search == substr($val, 0, strlen($search)));
- break;
- default:
- $got = (strpos($val, $search) !== false);
- break;
- }
-
- if ($got) {
- $this->result->add($rec);
- $this->result->count++;
- break 2;
- }
- }
- }
- }
-
- return $this->result;
- }
-
- // use AND operator for advanced searches
- $filter = is_array($value) ? '(&' : '(|';
- // set wildcards
- $wp = $ws = '';
- if (!empty($this->prop['fuzzy_search']) && $mode != 1) {
- $ws = '*';
- if (!$mode) {
- $wp = '*';
- }
- }
-
- if ($fields == '*')
- {
- // search_fields are required for fulltext search
- if (empty($this->prop['search_fields']))
- {
- $this->set_error(self::ERROR_SEARCH, 'nofulltextsearch');
- $this->result = new rcube_result_set();
- return $this->result;
- }
- if (is_array($this->prop['search_fields']))
- {
- foreach ($this->prop['search_fields'] as $field) {
- $filter .= "($field=$wp" . $this->_quote_string($value) . "$ws)";
- }
- }
- }
- else
- {
- foreach ((array)$fields as $idx => $field) {
- $val = is_array($value) ? $value[$idx] : $value;
- if ($attrs = $this->_map_field($field)) {
- if (count($attrs) > 1)
- $filter .= '(|';
- foreach ($attrs as $f)
- $filter .= "($f=$wp" . $this->_quote_string($val) . "$ws)";
- if (count($attrs) > 1)
- $filter .= ')';
- }
- }
- }
- $filter .= ')';
-
- // add required (non empty) fields filter
- $req_filter = '';
- foreach ((array)$required as $field) {
- if ($attrs = $this->_map_field($field)) {
- if (count($attrs) > 1)
- $req_filter .= '(|';
- foreach ($attrs as $f)
- $req_filter .= "($f=*)";
- if (count($attrs) > 1)
- $req_filter .= ')';
- }
- }
-
- if (!empty($req_filter))
- $filter = '(&' . $req_filter . $filter . ')';
-
- // avoid double-wildcard if $value is empty
- $filter = preg_replace('/\*+/', '*', $filter);
-
- // add general filter to query
- if (!empty($this->prop['filter']))
- $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['filter']) . ')' . $filter . ')';
-
- // set filter string and execute search
- $this->set_search_set($filter);
- $this->_exec_search();
-
- if ($select)
- $this->list_records();
- else
- $this->result = $this->count();
-
- return $this->result;
- }
-
-
- /**
- * Count number of available contacts in database
- *
- * @return object rcube_result_set Resultset with values for 'count' and 'first'
- */
- function count()
- {
- $count = 0;
- if ($this->conn && $this->ldap_result) {
- $count = $this->vlv_active ? $this->vlv_count : ldap_count_entries($this->conn, $this->ldap_result);
- }
- else if ($this->group_id && $this->group_data['dn']) {
- $count = count($this->list_group_members($this->group_data['dn'], true));
- }
- else if ($this->conn) {
- // We have a connection but no result set, attempt to get one.
- if (empty($this->filter)) {
- // The filter is not set, set it.
- $this->filter = $this->prop['filter'];
- }
-
- $count = (int) $this->_exec_search(true);
- }
-
- return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
- }
-
-
- /**
- * Return the last result set
- *
- * @return object rcube_result_set Current resultset or NULL if nothing selected yet
- */
- function get_result()
- {
- return $this->result;
- }
-
-
- /**
- * Get a specific contact record
- *
- * @param mixed Record identifier
- * @param boolean Return as associative array
- *
- * @return mixed Hash array or rcube_result_set with all record fields
- */
- function get_record($dn, $assoc=false)
- {
- $res = $this->result = null;
-
- if ($this->conn && $dn)
- {
- $dn = self::dn_decode($dn);
-
- $this->_debug("C: Read [dn: $dn] [(objectclass=*)]");
-
- if ($ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', array_values($this->fieldmap))) {
- $this->_debug("S: OK");
-
- $entry = ldap_first_entry($this->conn, $ldap_result);
-
- if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) {
- $rec = array_change_key_case($rec, CASE_LOWER);
- }
- }
- else {
- $this->_debug("S: ".ldap_error($this->conn));
- }
-
- // Use ldap_list to get subentries like country (c) attribute (#1488123)
- if (!empty($rec) && $this->sub_filter) {
- if ($entries = $this->ldap_list($dn, $this->sub_filter, array_keys($this->prop['sub_fields']))) {
- foreach ($entries as $entry) {
- $lrec = array_change_key_case($entry, CASE_LOWER);
- $rec = array_merge($lrec, $rec);
- }
- }
- }
-
- if (!empty($rec)) {
- // Add in the dn for the entry.
- $rec['dn'] = $dn;
- $res = $this->_ldap2result($rec);
- $this->result = new rcube_result_set();
- $this->result->add($res);
- }
- }
-
- return $assoc ? $res : $this->result;
- }
-
-
- /**
- * Check the given data before saving.
- * If input not valid, the message to display can be fetched using get_error()
- *
- * @param array Assoziative array with data to save
- * @param boolean Try to fix/complete record automatically
- * @return boolean True if input is valid, False if not.
- */
- public function validate(&$save_data, $autofix = false)
- {
- // validate e-mail addresses
- if (!parent::validate($save_data, $autofix)) {
- return false;
- }
-
- // check for name input
- if (empty($save_data['name'])) {
- $this->set_error(self::ERROR_VALIDATE, 'nonamewarning');
- return false;
- }
-
- // Verify that the required fields are set.
- $missing = null;
- $ldap_data = $this->_map_data($save_data);
- foreach ($this->prop['required_fields'] as $fld) {
- if (!isset($ldap_data[$fld]) || $ldap_data[$fld] === '') {
- $missing[$fld] = 1;
- }
- }
-
- if ($missing) {
- // try to complete record automatically
- if ($autofix) {
- $sn_field = $this->fieldmap['surname'];
- $fn_field = $this->fieldmap['firstname'];
- $mail_field = $this->fieldmap['email'];
-
- // try to extract surname and firstname from displayname
- $reverse_map = array_flip($this->fieldmap);
- $name_parts = preg_split('/[\s,.]+/', $save_data['name']);
-
- if ($sn_field && $missing[$sn_field]) {
- $save_data['surname'] = array_pop($name_parts);
- unset($missing[$sn_field]);
- }
-
- if ($fn_field && $missing[$fn_field]) {
- $save_data['firstname'] = array_shift($name_parts);
- unset($missing[$fn_field]);
- }
-
- // try to fix missing e-mail, very often on import
- // from vCard we have email:other only defined
- if ($mail_field && $missing[$mail_field]) {
- $emails = $this->get_col_values('email', $save_data, true);
- if (!empty($emails) && ($email = array_shift($emails))) {
- $save_data['email'] = $email;
- unset($missing[$mail_field]);
- }
- }
- }
-
- // TODO: generate message saying which fields are missing
- if (!empty($missing)) {
- $this->set_error(self::ERROR_VALIDATE, 'formincomplete');
- return false;
- }
- }
-
- return true;
- }
-
-
- /**
- * Create a new contact record
- *
- * @param array Hash array with save data
- *
- * @return encoded record ID on success, False on error
- */
- function insert($save_cols)
- {
- // Map out the column names to their LDAP ones to build the new entry.
- $newentry = $this->_map_data($save_cols);
- $newentry['objectClass'] = $this->prop['LDAP_Object_Classes'];
-
- // Verify that the required fields are set.
- $missing = null;
- foreach ($this->prop['required_fields'] as $fld) {
- if (!isset($newentry[$fld])) {
- $missing[] = $fld;
- }
- }
-
- // abort process if requiered fields are missing
- // TODO: generate message saying which fields are missing
- if ($missing) {
- $this->set_error(self::ERROR_VALIDATE, 'formincomplete');
- return false;
- }
-
- // Build the new entries DN.
- $dn = $this->prop['LDAP_rdn'].'='.$this->_quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->base_dn;
-
- // Remove attributes that need to be added separately (child objects)
- $xfields = array();
- if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) {
- foreach ($this->prop['sub_fields'] as $xf => $xclass) {
- if (!empty($newentry[$xf])) {
- $xfields[$xf] = $newentry[$xf];
- unset($newentry[$xf]);
- }
- }
- }
-
- if (!$this->ldap_add($dn, $newentry)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
-
- foreach ($xfields as $xidx => $xf) {
- $xdn = $xidx.'='.$this->_quote_string($xf).','.$dn;
- $xf = array(
- $xidx => $xf,
- 'objectClass' => (array) $this->prop['sub_fields'][$xidx],
- );
-
- $this->ldap_add($xdn, $xf);
- }
-
- $dn = self::dn_encode($dn);
-
- // add new contact to the selected group
- if ($this->group_id)
- $this->add_to_group($this->group_id, $dn);
-
- return $dn;
- }
-
-
- /**
- * Update a specific contact record
- *
- * @param mixed Record identifier
- * @param array Hash array with save data
- *
- * @return boolean True on success, False on error
- */
- function update($id, $save_cols)
- {
- $record = $this->get_record($id, true);
-
- $newdata = array();
- $replacedata = array();
- $deletedata = array();
- $subdata = array();
- $subdeldata = array();
- $subnewdata = array();
-
- $ldap_data = $this->_map_data($save_cols);
- $old_data = $record['_raw_attrib'];
-
- // special handling of photo col
- if ($photo_fld = $this->fieldmap['photo']) {
- // undefined means keep old photo
- if (!array_key_exists('photo', $save_cols)) {
- $ldap_data[$photo_fld] = $record['photo'];
- }
- }
-
- foreach ($this->fieldmap as $col => $fld) {
- if ($fld) {
- $val = $ldap_data[$fld];
- $old = $old_data[$fld];
- // remove empty array values
- if (is_array($val))
- $val = array_filter($val);
- // $this->_map_data() result and _raw_attrib use different format
- // make sure comparing array with one element with a string works as expected
- if (is_array($old) && count($old) == 1 && !is_array($val)) {
- $old = array_pop($old);
- }
- if (is_array($val) && count($val) == 1 && !is_array($old)) {
- $val = array_pop($val);
- }
- // Subentries must be handled separately
- if (!empty($this->prop['sub_fields']) && isset($this->prop['sub_fields'][$fld])) {
- if ($old != $val) {
- if ($old !== null) {
- $subdeldata[$fld] = $old;
- }
- if ($val) {
- $subnewdata[$fld] = $val;
- }
- }
- else if ($old !== null) {
- $subdata[$fld] = $old;
- }
- continue;
- }
-
- // The field does exist compare it to the ldap record.
- if ($old != $val) {
- // Changed, but find out how.
- if ($old === null) {
- // Field was not set prior, need to add it.
- $newdata[$fld] = $val;
- }
- else if ($val == '') {
- // Field supplied is empty, verify that it is not required.
- if (!in_array($fld, $this->prop['required_fields'])) {
- // ...It is not, safe to clear.
- // #1488420: Workaround "ldap_mod_del(): Modify: Inappropriate matching in..."
- // jpegPhoto attribute require an array() here. It looks to me that it works for other attribs too
- $deletedata[$fld] = array();
- //$deletedata[$fld] = $old_data[$fld];
- }
- }
- else {
- // The data was modified, save it out.
- $replacedata[$fld] = $val;
- }
- } // end if
- } // end if
- } // end foreach
-
- // console($old_data, $ldap_data, '----', $newdata, $replacedata, $deletedata, '----', $subdata, $subnewdata, $subdeldata);
-
- $dn = self::dn_decode($id);
-
- // Update the entry as required.
- if (!empty($deletedata)) {
- // Delete the fields.
- if (!$this->ldap_mod_del($dn, $deletedata)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
- } // end if
-
- if (!empty($replacedata)) {
- // Handle RDN change
- if ($replacedata[$this->prop['LDAP_rdn']]) {
- $newdn = $this->prop['LDAP_rdn'].'='
- .$this->_quote_string($replacedata[$this->prop['LDAP_rdn']], true)
- .','.$this->base_dn;
- if ($dn != $newdn) {
- $newrdn = $this->prop['LDAP_rdn'].'='
- .$this->_quote_string($replacedata[$this->prop['LDAP_rdn']], true);
- unset($replacedata[$this->prop['LDAP_rdn']]);
- }
- }
- // Replace the fields.
- if (!empty($replacedata)) {
- if (!$this->ldap_mod_replace($dn, $replacedata)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
- }
- } // end if
-
- // RDN change, we need to remove all sub-entries
- if (!empty($newrdn)) {
- $subdeldata = array_merge($subdeldata, $subdata);
- $subnewdata = array_merge($subnewdata, $subdata);
- }
-
- // remove sub-entries
- if (!empty($subdeldata)) {
- foreach ($subdeldata as $fld => $val) {
- $subdn = $fld.'='.$this->_quote_string($val).','.$dn;
- if (!$this->ldap_delete($subdn)) {
- return false;
- }
- }
- }
-
- if (!empty($newdata)) {
- // Add the fields.
- if (!$this->ldap_mod_add($dn, $newdata)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
- } // end if
-
- // Handle RDN change
- if (!empty($newrdn)) {
- if (!$this->ldap_rename($dn, $newrdn, null, true)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
-
- $dn = self::dn_encode($dn);
- $newdn = self::dn_encode($newdn);
-
- // change the group membership of the contact
- if ($this->groups) {
- $group_ids = $this->get_record_groups($dn);
- foreach ($group_ids as $group_id)
- {
- $this->remove_from_group($group_id, $dn);
- $this->add_to_group($group_id, $newdn);
- }
- }
-
- $dn = self::dn_decode($newdn);
- }
-
- // add sub-entries
- if (!empty($subnewdata)) {
- foreach ($subnewdata as $fld => $val) {
- $subdn = $fld.'='.$this->_quote_string($val).','.$dn;
- $xf = array(
- $fld => $val,
- 'objectClass' => (array) $this->prop['sub_fields'][$fld],
- );
- $this->ldap_add($subdn, $xf);
- }
- }
-
- return $newdn ? $newdn : true;
- }
-
-
- /**
- * Mark one or more contact records as deleted
- *
- * @param array Record identifiers
- * @param boolean Remove record(s) irreversible (unsupported)
- *
- * @return boolean True on success, False on error
- */
- function delete($ids, $force=true)
- {
- if (!is_array($ids)) {
- // Not an array, break apart the encoded DNs.
- $ids = explode(',', $ids);
- } // end if
-
- foreach ($ids as $id) {
- $dn = self::dn_decode($id);
-
- // Need to delete all sub-entries first
- if ($this->sub_filter) {
- if ($entries = $this->ldap_list($dn, $this->sub_filter)) {
- foreach ($entries as $entry) {
- if (!$this->ldap_delete($entry['dn'])) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
- }
- }
- }
-
- // Delete the record.
- if (!$this->ldap_delete($dn)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
-
- // remove contact from all groups where he was member
- if ($this->groups) {
- $dn = self::dn_encode($dn);
- $group_ids = $this->get_record_groups($dn);
- foreach ($group_ids as $group_id) {
- $this->remove_from_group($group_id, $dn);
- }
- }
- } // end foreach
-
- return count($ids);
- }
-
-
- /**
- * Remove all contact records
- */
- function delete_all()
- {
- //searching for contact entries
- $dn_list = $this->ldap_list($this->base_dn, $this->prop['filter'] ? $this->prop['filter'] : '(objectclass=*)');
-
- if (!empty($dn_list)) {
- foreach ($dn_list as $idx => $entry) {
- $dn_list[$idx] = self::dn_encode($entry['dn']);
- }
- $this->delete($dn_list);
- }
- }
-
-
- /**
- * Execute the LDAP search based on the stored credentials
- */
- private function _exec_search($count = false)
- {
- if ($this->ready)
- {
- $filter = $this->filter ? $this->filter : '(objectclass=*)';
- $function = $this->_scope2func($this->prop['scope'], $ns_function);
-
- $this->_debug("C: Search [$filter][dn: $this->base_dn]");
-
- // when using VLV, we get the total count by...
- if (!$count && $function != 'ldap_read' && $this->prop['vlv'] && !$this->group_id) {
- // ...either reading numSubOrdinates attribute
- if ($this->prop['numsub_filter'] && ($result_count = @$ns_function($this->conn, $this->base_dn, $this->prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) {
- $counts = ldap_get_entries($this->conn, $result_count);
- for ($this->vlv_count = $j = 0; $j < $counts['count']; $j++)
- $this->vlv_count += $counts[$j]['numsubordinates'][0];
- $this->_debug("D: total numsubordinates = " . $this->vlv_count);
- }
- else if (!function_exists('ldap_parse_virtuallist_control')) // ...or by fetching all records dn and count them
- $this->vlv_count = $this->_exec_search(true);
-
- $this->vlv_active = $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size);
- }
-
- // only fetch dn for count (should keep the payload low)
- $attrs = $count ? array('dn') : array_values($this->fieldmap);
- if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter,
- $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
- ) {
- // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result
- if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
- if (ldap_parse_result($this->conn, $this->ldap_result,
- $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)
- ) {
- ldap_parse_virtuallist_control($this->conn, $serverctrls,
- $last_offset, $this->vlv_count, $vresult);
- $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count");
- }
- else {
- $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn)));
- }
- }
-
- $entries_count = ldap_count_entries($this->conn, $this->ldap_result);
- $this->_debug("S: $entries_count record(s)");
-
- return $count ? $entries_count : true;
- }
- else {
- $this->_debug("S: ".ldap_error($this->conn));
- }
- }
-
- return false;
- }
-
- /**
- * Choose the right PHP function according to scope property
- */
- private function _scope2func($scope, &$ns_function = null)
- {
- switch ($scope) {
- case 'sub':
- $function = $ns_function = 'ldap_search';
- break;
- case 'base':
- $function = $ns_function = 'ldap_read';
- break;
- default:
- $function = 'ldap_list';
- $ns_function = 'ldap_read';
- break;
- }
-
- return $function;
- }
-
- /**
- * Set server controls for Virtual List View (paginated listing)
- */
- private function _vlv_set_controls($prop, $list_page, $page_size, $search = null)
- {
- $sort_ctrl = array('oid' => "1.2.840.113556.1.4.473", 'value' => $this->_sort_ber_encode((array)$prop['sort']));
- $vlv_ctrl = array('oid' => "2.16.840.1.113730.3.4.9", 'value' => $this->_vlv_ber_encode(($offset = ($list_page-1) * $page_size + 1), $page_size, $search), 'iscritical' => true);
-
- $sort = (array)$prop['sort'];
- $this->_debug("C: set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value'])) . " ($sort[0]);"
- . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size)");
-
- if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, array($sort_ctrl, $vlv_ctrl))) {
- $this->_debug("S: ".ldap_error($this->conn));
- $this->set_error(self::ERROR_SEARCH, 'vlvnotsupported');
- return false;
- }
-
- return true;
- }
-
-
- /**
- * Converts LDAP entry into an array
- */
- private function _ldap2result($rec)
- {
- $out = array();
-
- if ($rec['dn'])
- $out[$this->primary_key] = self::dn_encode($rec['dn']);
-
- foreach ($this->fieldmap as $rf => $lf)
- {
- for ($i=0; $i < $rec[$lf]['count']; $i++) {
- if (!($value = $rec[$lf][$i]))
- continue;
-
- list($col, $subtype) = explode(':', $rf);
- $out['_raw_attrib'][$lf][$i] = $value;
-
- if ($col == 'email' && $this->mail_domain && !strpos($value, '@'))
- $out[$rf][] = sprintf('%s@%s', $value, $this->mail_domain);
- else if (in_array($col, array('street','zipcode','locality','country','region')))
- $out['address'.($subtype?':':'').$subtype][$i][$col] = $value;
- else if ($col == 'address' && strpos($value, '$') !== false) // address data is represented as string separated with $
- list($out[$rf][$i]['street'], $out[$rf][$i]['locality'], $out[$rf][$i]['zipcode'], $out[$rf][$i]['country']) = explode('$', $value);
- else if ($rec[$lf]['count'] > 1)
- $out[$rf][] = $value;
- else
- $out[$rf] = $value;
- }
-
- // Make sure name fields aren't arrays (#1488108)
- if (is_array($out[$rf]) && in_array($rf, array('name', 'surname', 'firstname', 'middlename', 'nickname'))) {
- $out[$rf] = $out['_raw_attrib'][$lf] = $out[$rf][0];
- }
- }
-
- return $out;
- }
-
-
- /**
- * Return LDAP attribute(s) for the given field
- */
- private function _map_field($field)
- {
- return (array)$this->coltypes[$field]['attributes'];
- }
-
-
- /**
- * Convert a record data set into LDAP field attributes
- */
- private function _map_data($save_cols)
- {
- // flatten composite fields first
- foreach ($this->coltypes as $col => $colprop) {
- if (is_array($colprop['childs']) && ($values = $this->get_col_values($col, $save_cols, false))) {
- foreach ($values as $subtype => $childs) {
- $subtype = $subtype ? ':'.$subtype : '';
- foreach ($childs as $i => $child_values) {
- foreach ((array)$child_values as $childcol => $value) {
- $save_cols[$childcol.$subtype][$i] = $value;
- }
- }
- }
- }
-
- // if addresses are to be saved as serialized string, do so
- if (is_array($colprop['serialized'])) {
- foreach ($colprop['serialized'] as $subtype => $delim) {
- $key = $col.':'.$subtype;
- foreach ((array)$save_cols[$key] as $i => $val)
- $save_cols[$key][$i] = join($delim, array($val['street'], $val['locality'], $val['zipcode'], $val['country']));
- }
- }
- }
-
- $ldap_data = array();
- foreach ($this->fieldmap as $rf => $fld) {
- $val = $save_cols[$rf];
-
- // check for value in base field (eg.g email instead of email:foo)
- list($col, $subtype) = explode(':', $rf);
- if (!$val && !empty($save_cols[$col])) {
- $val = $save_cols[$col];
- unset($save_cols[$col]); // only use this value once
- }
- else if (!$val && !$subtype) { // extract values from subtype cols
- $val = $this->get_col_values($col, $save_cols, true);
- }
-
- if (is_array($val))
- $val = array_filter($val); // remove empty entries
- if ($fld && $val) {
- // The field does exist, add it to the entry.
- $ldap_data[$fld] = $val;
- }
- }
-
- return $ldap_data;
- }
-
-
- /**
- * Returns unified attribute name (resolving aliases)
- */
- private static function _attr_name($namev)
- {
- // list of known attribute aliases
- static $aliases = array(
- 'gn' => 'givenname',
- 'rfc822mailbox' => 'email',
- 'userid' => 'uid',
- 'emailaddress' => 'email',
- 'pkcs9email' => 'email',
- );
-
- list($name, $limit) = explode(':', $namev, 2);
- $suffix = $limit ? ':'.$limit : '';
-
- return (isset($aliases[$name]) ? $aliases[$name] : $name) . $suffix;
- }
-
-
- /**
- * Prints debug info to the log
- */
- private function _debug($str)
- {
- if ($this->debug) {
- rcube::write_log('ldap', $str);
- }
- }
-
-
- /**
- * Activate/deactivate debug mode
- *
- * @param boolean $dbg True if LDAP commands should be logged
- * @access public
- */
- function set_debug($dbg = true)
- {
- $this->debug = $dbg;
- }
-
-
- /**
- * Quotes attribute value string
- *
- * @param string $str Attribute value
- * @param bool $dn True if the attribute is a DN
- *
- * @return string Quoted string
- */
- private static function _quote_string($str, $dn=false)
- {
- // take firt entry if array given
- if (is_array($str))
- $str = reset($str);
-
- if ($dn)
- $replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c',
- '>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', '"'=>'\22', '#'=>'\23');
- else
- $replace = array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c',
- '/'=>'\2f');
-
- return strtr($str, $replace);
- }
-
-
- /**
- * Setter for the current group
- * (empty, has to be re-implemented by extending class)
- */
- function set_group($group_id)
- {
- if ($group_id)
- {
- if (($group_cache = $this->cache->get('groups')) === null)
- $group_cache = $this->_fetch_groups();
-
- $this->group_id = $group_id;
- $this->group_data = $group_cache[$group_id];
- }
- else
- {
- $this->group_id = 0;
- $this->group_data = null;
- }
- }
-
- /**
- * List all active contact groups of this source
- *
- * @param string Optional search string to match group name
- * @return array Indexed list of contact groups, each a hash array
- */
- function list_groups($search = null)
- {
- if (!$this->groups)
- return array();
-
- // use cached list for searching
- $this->cache->expunge();
- if (!$search || ($group_cache = $this->cache->get('groups')) === null)
- $group_cache = $this->_fetch_groups();
-
- $groups = array();
- if ($search) {
- $search = mb_strtolower($search);
- foreach ($group_cache as $group) {
- if (strpos(mb_strtolower($group['name']), $search) !== false)
- $groups[] = $group;
- }
- }
- else
- $groups = $group_cache;
-
- return array_values($groups);
- }
-
- /**
- * Fetch groups from server
- */
- private function _fetch_groups($vlv_page = 0)
- {
- $base_dn = $this->groups_base_dn;
- $filter = $this->prop['groups']['filter'];
- $name_attr = $this->prop['groups']['name_attr'];
- $email_attr = $this->prop['groups']['email_attr'] ? $this->prop['groups']['email_attr'] : 'mail';
- $sort_attrs = $this->prop['groups']['sort'] ? (array)$this->prop['groups']['sort'] : array($name_attr);
- $sort_attr = $sort_attrs[0];
-
- $this->_debug("C: Search [$filter][dn: $base_dn]");
-
- // use vlv to list groups
- if ($this->prop['groups']['vlv']) {
- $page_size = 200;
- if (!$this->prop['groups']['sort'])
- $this->prop['groups']['sort'] = $sort_attrs;
- $vlv_active = $this->_vlv_set_controls($this->prop['groups'], $vlv_page+1, $page_size);
- }
-
- $function = $this->_scope2func($this->prop['groups']['scope'], $ns_function);
- $res = @$function($this->conn, $base_dn, $filter, array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr)));
- if ($res === false)
- {
- $this->_debug("S: ".ldap_error($this->conn));
- return array();
- }
-
- $ldap_data = ldap_get_entries($this->conn, $res);
- $this->_debug("S: ".ldap_count_entries($this->conn, $res)." record(s)");
-
- $groups = array();
- $group_sortnames = array();
- $group_count = $ldap_data["count"];
- for ($i=0; $i < $group_count; $i++)
- {
- $group_name = is_array($ldap_data[$i][$name_attr]) ? $ldap_data[$i][$name_attr][0] : $ldap_data[$i][$name_attr];
- $group_id = self::dn_encode($group_name);
- $groups[$group_id]['ID'] = $group_id;
- $groups[$group_id]['dn'] = $ldap_data[$i]['dn'];
- $groups[$group_id]['name'] = $group_name;
- $groups[$group_id]['member_attr'] = $this->get_group_member_attr($ldap_data[$i]['objectclass']);
-
- // list email attributes of a group
- for ($j=0; $ldap_data[$i][$email_attr] && $j < $ldap_data[$i][$email_attr]['count']; $j++) {
- if (strpos($ldap_data[$i][$email_attr][$j], '@') > 0)
- $groups[$group_id]['email'][] = $ldap_data[$i][$email_attr][$j];
- }
-
- $group_sortnames[] = mb_strtolower($ldap_data[$i][$sort_attr][0]);
- }
-
- // recursive call can exit here
- if ($vlv_page > 0)
- return $groups;
-
- // call recursively until we have fetched all groups
- while ($vlv_active && $group_count == $page_size)
- {
- $next_page = $this->_fetch_groups(++$vlv_page);
- $groups = array_merge($groups, $next_page);
- $group_count = count($next_page);
- }
-
- // when using VLV the list of groups is already sorted
- if (!$this->prop['groups']['vlv'])
- array_multisort($group_sortnames, SORT_ASC, SORT_STRING, $groups);
-
- // cache this
- $this->cache->set('groups', $groups);
-
- return $groups;
- }
-
- /**
- * Get group properties such as name and email address(es)
- *
- * @param string Group identifier
- * @return array Group properties as hash array
- */
- function get_group($group_id)
- {
- if (($group_cache = $this->cache->get('groups')) === null)
- $group_cache = $this->_fetch_groups();
-
- $group_data = $group_cache[$group_id];
- unset($group_data['dn'], $group_data['member_attr']);
-
- return $group_data;
- }
-
- /**
- * Create a contact group with the given name
- *
- * @param string The group name
- * @return mixed False on error, array with record props in success
- */
- function create_group($group_name)
- {
- $base_dn = $this->groups_base_dn;
- $new_dn = "cn=$group_name,$base_dn";
- $new_gid = self::dn_encode($group_name);
- $member_attr = $this->get_group_member_attr();
- $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn';
-
- $new_entry = array(
- 'objectClass' => $this->prop['groups']['object_classes'],
- $name_attr => $group_name,
- $member_attr => '',
- );
-
- if (!$this->ldap_add($new_dn, $new_entry)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
-
- $this->cache->remove('groups');
-
- return array('id' => $new_gid, 'name' => $group_name);
- }
-
- /**
- * Delete the given group and all linked group members
- *
- * @param string Group identifier
- * @return boolean True on success, false if no data was changed
- */
- function delete_group($group_id)
- {
- if (($group_cache = $this->cache->get('groups')) === null)
- $group_cache = $this->_fetch_groups();
-
- $base_dn = $this->groups_base_dn;
- $group_name = $group_cache[$group_id]['name'];
- $del_dn = "cn=$group_name,$base_dn";
-
- if (!$this->ldap_delete($del_dn)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
-
- $this->cache->remove('groups');
-
- return true;
- }
-
- /**
- * Rename a specific contact group
- *
- * @param string Group identifier
- * @param string New name to set for this group
- * @param string 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, $new_name, &$new_gid)
- {
- if (($group_cache = $this->cache->get('groups')) === null)
- $group_cache = $this->_fetch_groups();
-
- $base_dn = $this->groups_base_dn;
- $group_name = $group_cache[$group_id]['name'];
- $old_dn = "cn=$group_name,$base_dn";
- $new_rdn = "cn=$new_name";
- $new_gid = self::dn_encode($new_name);
-
- if (!$this->ldap_rename($old_dn, $new_rdn, null, true)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return false;
- }
-
- $this->cache->remove('groups');
-
- return $new_name;
- }
-
- /**
- * Add the given contact records the a certain group
- *
- * @param string Group identifier
- * @param array List of contact identifiers to be added
- * @return int Number of contacts added
- */
- function add_to_group($group_id, $contact_ids)
- {
- if (($group_cache = $this->cache->get('groups')) === null)
- $group_cache = $this->_fetch_groups();
-
- if (!is_array($contact_ids))
- $contact_ids = explode(',', $contact_ids);
-
- $base_dn = $this->groups_base_dn;
- $group_name = $group_cache[$group_id]['name'];
- $member_attr = $group_cache[$group_id]['member_attr'];
- $group_dn = "cn=$group_name,$base_dn";
-
- $new_attrs = array();
- foreach ($contact_ids as $id)
- $new_attrs[$member_attr][] = self::dn_decode($id);
-
- if (!$this->ldap_mod_add($group_dn, $new_attrs)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return 0;
- }
-
- $this->cache->remove('groups');
-
- return count($new_attrs['member']);
- }
-
- /**
- * Remove the given contact records from a certain group
- *
- * @param string Group identifier
- * @param array List of contact identifiers to be removed
- * @return int Number of deleted group members
- */
- function remove_from_group($group_id, $contact_ids)
- {
- if (($group_cache = $this->cache->get('groups')) === null)
- $group_cache = $this->_fetch_groups();
-
- $base_dn = $this->groups_base_dn;
- $group_name = $group_cache[$group_id]['name'];
- $member_attr = $group_cache[$group_id]['member_attr'];
- $group_dn = "cn=$group_name,$base_dn";
-
- $del_attrs = array();
- foreach (explode(",", $contact_ids) as $id)
- $del_attrs[$member_attr][] = self::dn_decode($id);
-
- if (!$this->ldap_mod_del($group_dn, $del_attrs)) {
- $this->set_error(self::ERROR_SAVING, 'errorsaving');
- return 0;
- }
-
- $this->cache->remove('groups');
-
- return count($del_attrs['member']);
- }
-
- /**
- * Get group assignments of a specific contact record
- *
- * @param mixed Record identifier
- *
- * @return array List of assigned groups as ID=>Name pairs
- * @since 0.5-beta
- */
- function get_record_groups($contact_id)
- {
- if (!$this->groups)
- return array();
-
- $base_dn = $this->groups_base_dn;
- $contact_dn = self::dn_decode($contact_id);
- $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn';
- $member_attr = $this->get_group_member_attr();
- $add_filter = '';
- if ($member_attr != 'member' && $member_attr != 'uniqueMember')
- $add_filter = "($member_attr=$contact_dn)";
- $filter = strtr("(|(member=$contact_dn)(uniqueMember=$contact_dn)$add_filter)", array('\\' => '\\\\'));
-
- $this->_debug("C: Search [$filter][dn: $base_dn]");
-
- $res = @ldap_search($this->conn, $base_dn, $filter, array($name_attr));
- if ($res === false)
- {
- $this->_debug("S: ".ldap_error($this->conn));
- return array();
- }
- $ldap_data = ldap_get_entries($this->conn, $res);
- $this->_debug("S: ".ldap_count_entries($this->conn, $res)." record(s)");
-
- $groups = array();
- for ($i=0; $i<$ldap_data["count"]; $i++)
- {
- $group_name = $ldap_data[$i][$name_attr][0];
- $group_id = self::dn_encode($group_name);
- $groups[$group_id] = $group_id;
- }
- return $groups;
- }
-
- /**
- * Detects group member attribute name
- */
- private function get_group_member_attr($object_classes = array())
- {
- if (empty($object_classes)) {
- $object_classes = $this->prop['groups']['object_classes'];
- }
- if (!empty($object_classes)) {
- foreach ((array)$object_classes as $oc) {
- switch (strtolower($oc)) {
- case 'group':
- case 'groupofnames':
- case 'kolabgroupofnames':
- $member_attr = 'member';
- break;
-
- case 'groupofuniquenames':
- case 'kolabgroupofuniquenames':
- $member_attr = 'uniqueMember';
- break;
- }
- }
- }
-
- if (!empty($member_attr)) {
- return $member_attr;
- }
-
- if (!empty($this->prop['groups']['member_attr'])) {
- return $this->prop['groups']['member_attr'];
- }
-
- return 'member';
- }
-
-
- /**
- * Generate BER encoded string for Virtual List View option
- *
- * @param integer List offset (first record)
- * @param integer Records per page
- * @return string BER encoded option value
- */
- private function _vlv_ber_encode($offset, $rpp, $search = '')
- {
- # this string is ber-encoded, php will prefix this value with:
- # 04 (octet string) and 10 (length of 16 bytes)
- # the code behind this string is broken down as follows:
- # 30 = ber sequence with a length of 0e (14) bytes following
- # 02 = type integer (in two's complement form) with 2 bytes following (beforeCount): 01 00 (ie 0)
- # 02 = type integer (in two's complement form) with 2 bytes following (afterCount): 01 18 (ie 25-1=24)
- # a0 = type context-specific/constructed with a length of 06 (6) bytes following
- # 02 = type integer with 2 bytes following (offset): 01 01 (ie 1)
- # 02 = type integer with 2 bytes following (contentCount): 01 00
-
- # whith a search string present:
- # 81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here)
- # 81 indicates a user string is present where as a a0 indicates just a offset search
- # 81 = type context-specific/constructed with a length of 06 (6) bytes following
-
- # the following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the
- # encoding of integer values (note: these values are in
- # two-complement form so since offset will never be negative bit 8 of the
- # leftmost octet should never by set to 1):
- # 8.3.2: If the contents octets of an integer value encoding consist
- # of more than one octet, then the bits of the first octet (rightmost) and bit 8
- # of the second (to the left of first octet) octet:
- # a) shall not all be ones; and
- # b) shall not all be zero
-
- if ($search)
- {
- $search = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search);
- $ber_val = self::_string2hex($search);
- $str = self::_ber_addseq($ber_val, '81');
- }
- else
- {
- # construct the string from right to left
- $str = "020100"; # contentCount
-
- $ber_val = self::_ber_encode_int($offset); // returns encoded integer value in hex format
-
- // calculate octet length of $ber_val
- $str = self::_ber_addseq($ber_val, '02') . $str;
-
- // now compute length over $str
- $str = self::_ber_addseq($str, 'a0');
- }
-
- // now tack on records per page
- $str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str;
-
- // now tack on sequence identifier and length
- $str = self::_ber_addseq($str, '30');
-
- return pack('H'.strlen($str), $str);
- }
-
-
- /**
- * create ber encoding for sort control
- *
- * @param array List of cols to sort by
- * @return string BER encoded option value
- */
- private function _sort_ber_encode($sortcols)
- {
- $str = '';
- foreach (array_reverse((array)$sortcols) as $col) {
- $ber_val = self::_string2hex($col);
-
- # 30 = ber sequence with a length of octet value
- # 04 = octet string with a length of the ascii value
- $oct = self::_ber_addseq($ber_val, '04');
- $str = self::_ber_addseq($oct, '30') . $str;
- }
-
- // now tack on sequence identifier and length
- $str = self::_ber_addseq($str, '30');
-
- return pack('H'.strlen($str), $str);
- }
-
- /**
- * Add BER sequence with correct length and the given identifier
- */
- private static function _ber_addseq($str, $identifier)
- {
- $len = dechex(strlen($str)/2);
- if (strlen($len) % 2 != 0)
- $len = '0'.$len;
-
- return $identifier . $len . $str;
- }
-
- /**
- * Returns BER encoded integer value in hex format
- */
- private static function _ber_encode_int($offset)
- {
- $val = dechex($offset);
- $prefix = '';
-
- // check if bit 8 of high byte is 1
- if (preg_match('/^[89abcdef]/', $val))
- $prefix = '00';
-
- if (strlen($val)%2 != 0)
- $prefix .= '0';
-
- return $prefix . $val;
- }
-
- /**
- * Returns ascii string encoded in hex
- */
- private static function _string2hex($str)
- {
- $hex = '';
- for ($i=0; $i < strlen($str); $i++)
- $hex .= dechex(ord($str[$i]));
- return $hex;
- }
-
- /**
- * HTML-safe DN string encoding
- *
- * @param string $str DN string
- *
- * @return string Encoded HTML identifier string
- */
- static function dn_encode($str)
- {
- // @TODO: to make output string shorter we could probably
- // remove dc=* items from it
- return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
- }
-
- /**
- * Decodes DN string encoded with _dn_encode()
- *
- * @param string $str Encoded HTML identifier string
- *
- * @return string DN string
- */
- static function dn_decode($str)
- {
- $str = str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '=', STR_PAD_RIGHT);
- return base64_decode($str);
- }
-
- /**
- * Wrapper for ldap_add()
- */
- protected function ldap_add($dn, $entry)
- {
- $this->_debug("C: Add [dn: $dn]: ".print_r($entry, true));
-
- $res = ldap_add($this->conn, $dn, $entry);
- if ($res === false) {
- $this->_debug("S: ".ldap_error($this->conn));
- return false;
- }
-
- $this->_debug("S: OK");
- return true;
- }
-
- /**
- * Wrapper for ldap_delete()
- */
- protected function ldap_delete($dn)
- {
- $this->_debug("C: Delete [dn: $dn]");
-
- $res = ldap_delete($this->conn, $dn);
- if ($res === false) {
- $this->_debug("S: ".ldap_error($this->conn));
- return false;
- }
-
- $this->_debug("S: OK");
- return true;
- }
-
- /**
- * Wrapper for ldap_mod_replace()
- */
- protected function ldap_mod_replace($dn, $entry)
- {
- $this->_debug("C: Replace [dn: $dn]: ".print_r($entry, true));
-
- if (!ldap_mod_replace($this->conn, $dn, $entry)) {
- $this->_debug("S: ".ldap_error($this->conn));
- return false;
- }
-
- $this->_debug("S: OK");
- return true;
- }
-
- /**
- * Wrapper for ldap_mod_add()
- */
- protected function ldap_mod_add($dn, $entry)
- {
- $this->_debug("C: Add [dn: $dn]: ".print_r($entry, true));
-
- if (!ldap_mod_add($this->conn, $dn, $entry)) {
- $this->_debug("S: ".ldap_error($this->conn));
- return false;
- }
-
- $this->_debug("S: OK");
- return true;
- }
-
- /**
- * Wrapper for ldap_mod_del()
- */
- protected function ldap_mod_del($dn, $entry)
- {
- $this->_debug("C: Delete [dn: $dn]: ".print_r($entry, true));
-
- if (!ldap_mod_del($this->conn, $dn, $entry)) {
- $this->_debug("S: ".ldap_error($this->conn));
- return false;
- }
-
- $this->_debug("S: OK");
- return true;
- }
-
- /**
- * Wrapper for ldap_rename()
- */
- protected function ldap_rename($dn, $newrdn, $newparent = null, $deleteoldrdn = true)
- {
- $this->_debug("C: Rename [dn: $dn] [dn: $newrdn]");
-
- if (!ldap_rename($this->conn, $dn, $newrdn, $newparent, $deleteoldrdn)) {
- $this->_debug("S: ".ldap_error($this->conn));
- return false;
- }
-
- $this->_debug("S: OK");
- return true;
- }
-
- /**
- * Wrapper for ldap_list()
- */
- protected function ldap_list($dn, $filter, $attrs = array(''))
- {
- $list = array();
- $this->_debug("C: List [dn: $dn] [{$filter}]");
-
- if ($result = ldap_list($this->conn, $dn, $filter, $attrs)) {
- $list = ldap_get_entries($this->conn, $result);
-
- if ($list === false) {
- $this->_debug("S: ".ldap_error($this->conn));
- return array();
- }
-
- $count = $list['count'];
- unset($list['count']);
-
- $this->_debug("S: $count record(s)");
- }
- else {
- $this->_debug("S: ".ldap_error($this->conn));
- }
-
- return $list;
- }
-
-}
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
deleted file mode 100644
index 38d18171c..000000000
--- a/program/include/rcube_message.php
+++ /dev/null
@@ -1,760 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_message.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2010, 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 mail message with all its data |
- | and related functions |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Logical representation of a mail message with all its data
- * and related functions
- *
- * @package Mail
- * @author Thomas Bruederli <roundcube@gmail.com>
- */
-class rcube_message
-{
- /**
- * Instace of framework class.
- *
- * @var rcube
- */
- private $app;
-
- /**
- * Instance of storage class
- *
- * @var rcube_storage
- */
- private $storage;
-
- /**
- * Instance of mime class
- *
- * @var rcube_mime
- */
- private $mime;
- private $opt = array();
- private $parse_alternative = false;
-
- public $uid;
- public $folder;
- public $headers;
- public $parts = array();
- public $mime_parts = array();
- public $inline_parts = array();
- public $attachments = array();
- public $subject = '';
- public $sender = null;
- public $is_safe = false;
-
-
- /**
- * __construct
- *
- * Provide a uid, and parse message structure.
- *
- * @param string $uid The message UID.
- * @param string $folder Folder name
- *
- * @see self::$app, self::$storage, self::$opt, self::$parts
- */
- function __construct($uid, $folder = null)
- {
- $this->uid = $uid;
- $this->app = rcube::get_instance();
- $this->storage = $this->app->get_storage();
- $this->folder = strlen($folder) ? $folder : $this->storage->get_folder();
- $this->storage->set_options(array('all_headers' => true));
-
- // Set current folder
- $this->storage->set_folder($this->folder);
-
- $this->headers = $this->storage->get_message($uid);
-
- if (!$this->headers)
- return;
-
- $this->mime = new rcube_mime($this->headers->charset);
-
- $this->subject = $this->mime->decode_mime_string($this->headers->subject);
- list(, $this->sender) = each($this->mime->decode_address_list($this->headers->from, 1));
-
- $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid]));
- $this->opt = array(
- 'safe' => $this->is_safe,
- 'prefer_html' => $this->app->config->get('prefer_html'),
- 'get_url' => $this->app->url(array(
- 'action' => 'get',
- 'mbox' => $this->storage->get_folder(),
- 'uid' => $uid))
- );
-
- if (!empty($this->headers->structure)) {
- $this->get_mime_numbers($this->headers->structure);
- $this->parse_structure($this->headers->structure);
- }
- else {
- $this->body = $this->storage->get_body($uid);
- }
-
- // notify plugins and let them analyze this structured message object
- $this->app->plugins->exec_hook('message_load', array('object' => $this));
- }
-
-
- /**
- * Return a (decoded) message header
- *
- * @param string $name Header name
- * @param bool $row Don't mime-decode the value
- * @return string Header value
- */
- public function get_header($name, $raw = false)
- {
- if (empty($this->headers))
- return null;
-
- if ($this->headers->$name)
- $value = $this->headers->$name;
- else if ($this->headers->others[$name])
- $value = $this->headers->others[$name];
-
- return $raw ? $value : $this->mime->decode_header($value);
- }
-
-
- /**
- * Set is_safe var and session data
- *
- * @param bool $safe enable/disable
- */
- public function set_safe($safe = true)
- {
- $this->is_safe = $safe;
- $_SESSION['safe_messages'][$this->uid] = $this->is_safe;
- }
-
-
- /**
- * Compose a valid URL for getting a message part
- *
- * @param string $mime_id Part MIME-ID
- * @return string URL or false if part does not exist
- */
- public function get_part_url($mime_id, $embed = false)
- {
- if ($this->mime_parts[$mime_id])
- return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1' : '');
- else
- return false;
- }
-
-
- /**
- * Get content of a specific part of this message
- *
- * @param string $mime_id Part MIME-ID
- * @param resource $fp File pointer to save the message part
- * @param boolean $skip_charset_conv Disables charset conversion
- *
- * @return string Part content
- */
- public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false)
- {
- if ($part = $this->mime_parts[$mime_id]) {
- // stored in message structure (winmail/inline-uuencode)
- if (!empty($part->body) || $part->encoding == 'stream') {
- if ($fp) {
- fwrite($fp, $part->body);
- }
- return $fp ? true : $part->body;
- }
-
- // get from IMAP
- $this->storage->set_folder($this->folder);
-
- return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv);
- }
- }
-
-
- /**
- * Determine if the message contains a HTML part
- *
- * @param bool $recursive Enables checking in all levels of the structure
- *
- * @return bool True if a HTML is available, False if not
- */
- function has_html_part($recursive = true)
- {
- // check all message parts
- foreach ($this->parts as $part) {
- if ($part->mimetype == 'text/html') {
- // Level check, we'll skip e.g. HTML attachments
- if (!$recursive) {
- $level = explode('.', $part->mime_id);
-
- // Skip if level too deep or part has a file name
- if (count($level) > 2 || $part->filename) {
- continue;
- }
-
- // HTML part can be on the lower level, if not...
- if (count($level) > 1) {
- array_pop($level);
- $parent = $this->mime_parts[join('.', $level)];
- // ... parent isn't multipart/alternative or related
- if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') {
- continue;
- }
- }
- }
-
- return true;
- }
- }
-
- return false;
- }
-
-
- /**
- * Return the first HTML part of this message
- *
- * @return string HTML message part content
- */
- function first_html_part()
- {
- // check all message parts
- foreach ($this->mime_parts as $pid => $part) {
- if ($part->mimetype == 'text/html') {
- return $this->get_part_content($pid);
- }
- }
- }
-
-
- /**
- * Return the first text part of this message
- *
- * @param rcube_message_part $part Reference to the part if found
- * @return string Plain text message/part content
- */
- function first_text_part(&$part=null)
- {
- // no message structure, return complete body
- if (empty($this->parts))
- return $this->body;
-
- // check all message parts
- foreach ($this->mime_parts as $mime_id => $part) {
- if ($part->mimetype == 'text/plain') {
- return $this->get_part_content($mime_id);
- }
- else if ($part->mimetype == 'text/html') {
- $out = $this->get_part_content($mime_id);
-
- // remove special chars encoding
- $trans = array_flip(get_html_translation_table(HTML_ENTITIES));
- $out = strtr($out, $trans);
-
- // create instance of html2text class
- $txt = new html2text($out);
- return $txt->get_text();
- }
- }
-
- $part = null;
- return null;
- }
-
-
- /**
- * Checks if part of the message is an attachment (or part of it)
- *
- * @param rcube_message_part $part Message part
- *
- * @return bool True if the part is an attachment part
- */
- public function is_attachment($part)
- {
- foreach ($this->attachments as $att_part) {
- if ($att_part->mime_id == $part->mime_id) {
- return true;
- }
-
- // check if the part is a subpart of another attachment part (message/rfc822)
- if ($att_part->mimetype == 'message/rfc822') {
- if (in_array($part, (array)$att_part->parts)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
-
- /**
- * Read the message structure returend by the IMAP server
- * and build flat lists of content parts and attachments
- *
- * @param rcube_message_part $structure Message structure node
- * @param bool $recursive True when called recursively
- */
- private function parse_structure($structure, $recursive = false)
- {
- // real content-type of message/rfc822 part
- if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype)
- $mimetype = $structure->real_mimetype;
- else
- $mimetype = $structure->mimetype;
-
- // show message headers
- if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) {
- $c = new stdClass;
- $c->type = 'headers';
- $c->headers = &$structure->headers;
- $this->parts[] = $c;
- }
-
- // Allow plugins to handle message parts
- $plugin = $this->app->plugins->exec_hook('message_part_structure',
- array('object' => $this, 'structure' => $structure,
- 'mimetype' => $mimetype, 'recursive' => $recursive));
-
- if ($plugin['abort'])
- return;
-
- $structure = $plugin['structure'];
- list($message_ctype_primary, $message_ctype_secondary) = explode('/', $plugin['mimetype']);
-
- // print body if message doesn't have multiple parts
- if ($message_ctype_primary == 'text' && !$recursive) {
- $structure->type = 'content';
- $this->parts[] = &$structure;
-
- // Parse simple (plain text) message body
- if ($message_ctype_secondary == 'plain')
- foreach ((array)$this->uu_decode($structure) as $uupart) {
- $this->mime_parts[$uupart->mime_id] = $uupart;
- $this->attachments[] = $uupart;
- }
- }
- // the same for pgp signed messages
- else if ($mimetype == 'application/pgp' && !$recursive) {
- $structure->type = 'content';
- $this->parts[] = &$structure;
- }
- // message contains (more than one!) alternative parts
- else if ($mimetype == 'multipart/alternative'
- && is_array($structure->parts) && count($structure->parts) > 1
- ) {
- // get html/plaintext parts
- $plain_part = $html_part = $print_part = $related_part = null;
-
- foreach ($structure->parts as $p => $sub_part) {
- $sub_mimetype = $sub_part->mimetype;
-
- // skip empty text parts
- if (!$sub_part->size && preg_match('#^text/(plain|html|enriched)$#', $sub_mimetype)) {
- continue;
- }
-
- // check if sub part is
- if ($sub_mimetype == 'text/plain')
- $plain_part = $p;
- else if ($sub_mimetype == 'text/html')
- $html_part = $p;
- else if ($sub_mimetype == 'text/enriched')
- $enriched_part = $p;
- else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative')))
- $related_part = $p;
- }
-
- // parse related part (alternative part could be in here)
- if ($related_part !== null && !$this->parse_alternative) {
- $this->parse_alternative = true;
- $this->parse_structure($structure->parts[$related_part], true);
- $this->parse_alternative = false;
-
- // if plain part was found, we should unset it if html is preferred
- if ($this->opt['prefer_html'] && count($this->parts))
- $plain_part = null;
- }
-
- // choose html/plain part to print
- if ($html_part !== null && $this->opt['prefer_html']) {
- $print_part = &$structure->parts[$html_part];
- }
- else if ($enriched_part !== null) {
- $print_part = &$structure->parts[$enriched_part];
- }
- else if ($plain_part !== null) {
- $print_part = &$structure->parts[$plain_part];
- }
-
- // add the right message body
- if (is_object($print_part)) {
- $print_part->type = 'content';
- $this->parts[] = $print_part;
- }
- // show plaintext warning
- else if ($html_part !== null && empty($this->parts)) {
- $c = new stdClass;
- $c->type = 'content';
- $c->ctype_primary = 'text';
- $c->ctype_secondary = 'plain';
- $c->mimetype = 'text/plain';
- $c->realtype = 'text/html';
-
- $this->parts[] = $c;
- }
-
- // add html part as attachment
- if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
- $html_part = &$structure->parts[$html_part];
- $html_part->mimetype = 'text/html';
-
- $this->attachments[] = $html_part;
- }
- }
- // this is an ecrypted message -> create a plaintext body with the according message
- else if ($mimetype == 'multipart/encrypted') {
- $p = new stdClass;
- $p->type = 'content';
- $p->ctype_primary = 'text';
- $p->ctype_secondary = 'plain';
- $p->mimetype = 'text/plain';
- $p->realtype = 'multipart/encrypted';
-
- $this->parts[] = $p;
- }
- // message contains multiple parts
- else if (is_array($structure->parts) && !empty($structure->parts)) {
- // iterate over parts
- for ($i=0; $i < count($structure->parts); $i++) {
- $mail_part = &$structure->parts[$i];
- $primary_type = $mail_part->ctype_primary;
- $secondary_type = $mail_part->ctype_secondary;
-
- // real content-type of message/rfc822
- if ($mail_part->real_mimetype) {
- $part_orig_mimetype = $mail_part->mimetype;
- $part_mimetype = $mail_part->real_mimetype;
- list($primary_type, $secondary_type) = explode('/', $part_mimetype);
- }
- else
- $part_mimetype = $mail_part->mimetype;
-
- // multipart/alternative
- if ($primary_type == 'multipart') {
- $this->parse_structure($mail_part, true);
-
- // list message/rfc822 as attachment as well (mostly .eml)
- if ($part_orig_mimetype == 'message/rfc822' && !empty($mail_part->filename))
- $this->attachments[] = $mail_part;
- }
- // part text/[plain|html] or delivery status
- else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') ||
- in_array($part_mimetype, array('message/delivery-status', 'text/rfc822-headers', 'message/disposition-notification'))
- ) {
- // Allow plugins to handle also this part
- $plugin = $this->app->plugins->exec_hook('message_part_structure',
- array('object' => $this, 'structure' => $mail_part,
- 'mimetype' => $part_mimetype, 'recursive' => true));
-
- if ($plugin['abort'])
- continue;
-
- if ($part_mimetype == 'text/html') {
- $got_html_part = true;
- }
-
- $mail_part = $plugin['structure'];
- list($primary_type, $secondary_type) = explode('/', $plugin['mimetype']);
-
- // add text part if it matches the prefs
- if (!$this->parse_alternative ||
- ($secondary_type == 'html' && $this->opt['prefer_html']) ||
- ($secondary_type == 'plain' && !$this->opt['prefer_html'])
- ) {
- $mail_part->type = 'content';
- $this->parts[] = $mail_part;
- }
-
- // list as attachment as well
- if (!empty($mail_part->filename)) {
- $this->attachments[] = $mail_part;
- }
- // list html part as attachment (here the part is most likely inside a multipart/related part)
- else if ($this->parse_alternative && ($secondary_type == 'html' && !$this->opt['prefer_html'])) {
- $this->attachments[] = $mail_part;
- }
- }
- // part message/*
- else if ($primary_type == 'message') {
- $this->parse_structure($mail_part, true);
-
- // list as attachment as well (mostly .eml)
- if (!empty($mail_part->filename))
- $this->attachments[] = $mail_part;
- }
- // ignore "virtual" protocol parts
- else if ($primary_type == 'protocol') {
- continue;
- }
- // part is Microsoft Outlook TNEF (winmail.dat)
- else if ($part_mimetype == 'application/ms-tnef') {
- foreach ((array)$this->tnef_decode($mail_part) as $tpart) {
- $this->mime_parts[$tpart->mime_id] = $tpart;
- $this->attachments[] = $tpart;
- }
- }
- // part is a file/attachment
- else if (preg_match('/^(inline|attach)/', $mail_part->disposition) ||
- $mail_part->headers['content-id'] ||
- ($mail_part->filename &&
- (empty($mail_part->disposition) || preg_match('/^[a-z0-9!#$&.+^_-]+$/i', $mail_part->disposition)))
- ) {
- // skip apple resource forks
- if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile')
- continue;
-
- // part belongs to a related message and is linked
- if ($mimetype == 'multipart/related'
- && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) {
- if ($mail_part->headers['content-id'])
- $mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']);
- if ($mail_part->headers['content-location'])
- $mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location'];
-
- $this->inline_parts[] = $mail_part;
- }
- // attachment encapsulated within message/rfc822 part needs further decoding (#1486743)
- else if ($part_orig_mimetype == 'message/rfc822') {
- $this->parse_structure($mail_part, true);
-
- // list as attachment as well (mostly .eml)
- if (!empty($mail_part->filename))
- $this->attachments[] = $mail_part;
- }
- // regular attachment with valid content type
- // (content-type name regexp according to RFC4288.4.2)
- else if (preg_match('/^[a-z0-9!#$&.+^_-]+\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) {
- if (!$mail_part->filename)
- $mail_part->filename = 'Part '.$mail_part->mime_id;
-
- $this->attachments[] = $mail_part;
- }
- // attachment with invalid content type
- // replace malformed content type with application/octet-stream (#1487767)
- else if ($mail_part->filename) {
- $mail_part->ctype_primary = 'application';
- $mail_part->ctype_secondary = 'octet-stream';
- $mail_part->mimetype = 'application/octet-stream';
-
- $this->attachments[] = $mail_part;
- }
- }
- // attachment part as message/rfc822 (#1488026)
- else if ($mail_part->mimetype == 'message/rfc822') {
- $this->parse_structure($mail_part);
- }
- }
-
- // if this was a related part try to resolve references
- if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) {
- $a_replaces = array();
- $img_regexp = '/^image\/(gif|jpe?g|png|tiff|bmp|svg)/';
-
- foreach ($this->inline_parts as $inline_object) {
- $part_url = $this->get_part_url($inline_object->mime_id, true);
- if ($inline_object->content_id)
- $a_replaces['cid:'.$inline_object->content_id] = $part_url;
- if ($inline_object->content_location) {
- $a_replaces[$inline_object->content_location] = $part_url;
- }
-
- if (!empty($inline_object->filename)) {
- // MS Outlook sends sometimes non-related attachments as related
- // In this case multipart/related message has only one text part
- // We'll add all such attachments to the attachments list
- if (!isset($got_html_part) && empty($inline_object->content_id)) {
- $this->attachments[] = $inline_object;
- }
- // MS Outlook sometimes also adds non-image attachments as related
- // We'll add all such attachments to the attachments list
- // Warning: some browsers support pdf in <img/>
- else if (!preg_match($img_regexp, $inline_object->mimetype)) {
- $this->attachments[] = $inline_object;
- }
- // @TODO: we should fetch HTML body and find attachment's content-id
- // to handle also image attachments without reference in the body
- // @TODO: should we list all image attachments in text mode?
- }
- }
-
- // add replace array to each content part
- // (will be applied later when part body is available)
- foreach ($this->parts as $i => $part) {
- if ($part->type == 'content')
- $this->parts[$i]->replaces = $a_replaces;
- }
- }
- }
- // message is a single part non-text
- else if ($structure->filename) {
- $this->attachments[] = $structure;
- }
- // message is a single part non-text (without filename)
- else if (preg_match('/application\//i', $mimetype)) {
- $structure->filename = 'Part '.$structure->mime_id;
- $this->attachments[] = $structure;
- }
- }
-
-
- /**
- * Fill aflat array with references to all parts, indexed by part numbers
- *
- * @param rcube_message_part $part Message body structure
- */
- private function get_mime_numbers(&$part)
- {
- if (strlen($part->mime_id))
- $this->mime_parts[$part->mime_id] = &$part;
-
- if (is_array($part->parts))
- for ($i=0; $i<count($part->parts); $i++)
- $this->get_mime_numbers($part->parts[$i]);
- }
-
-
- /**
- * Decode a Microsoft Outlook TNEF part (winmail.dat)
- *
- * @param rcube_message_part $part Message part to decode
- * @return array
- */
- function tnef_decode(&$part)
- {
- // @TODO: attachment may be huge, hadle it via file
- if (!isset($part->body)) {
- $this->storage->set_folder($this->folder);
- $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part);
- }
-
- $parts = array();
- $tnef = new tnef_decoder;
- $tnef_arr = $tnef->decompress($part->body);
-
- foreach ($tnef_arr as $pid => $winatt) {
- $tpart = new rcube_message_part;
-
- $tpart->filename = trim($winatt['name']);
- $tpart->encoding = 'stream';
- $tpart->ctype_primary = trim(strtolower($winatt['type']));
- $tpart->ctype_secondary = trim(strtolower($winatt['subtype']));
- $tpart->mimetype = $tpart->ctype_primary . '/' . $tpart->ctype_secondary;
- $tpart->mime_id = 'winmail.' . $part->mime_id . '.' . $pid;
- $tpart->size = $winatt['size'];
- $tpart->body = $winatt['stream'];
-
- $parts[] = $tpart;
- unset($tnef_arr[$pid]);
- }
-
- return $parts;
- }
-
-
- /**
- * Parse message body for UUencoded attachments bodies
- *
- * @param rcube_message_part $part Message part to decode
- * @return array
- */
- function uu_decode(&$part)
- {
- // @TODO: messages may be huge, hadle body via file
- if (!isset($part->body)) {
- $this->storage->set_folder($this->folder);
- $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part);
- }
-
- $parts = array();
- // FIXME: line length is max.65?
- $uu_regexp = '/begin [0-7]{3,4} ([^\n]+)\n/s';
-
- if (preg_match_all($uu_regexp, $part->body, $matches, PREG_SET_ORDER)) {
- // update message content-type
- $part->ctype_primary = 'multipart';
- $part->ctype_secondary = 'mixed';
- $part->mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
- $uu_endstring = "`\nend\n";
-
- // add attachments to the structure
- foreach ($matches as $pid => $att) {
- $startpos = strpos($part->body, $att[1]) + strlen($att[1]) + 1; // "\n"
- $endpos = strpos($part->body, $uu_endstring);
- $filebody = substr($part->body, $startpos, $endpos-$startpos);
-
- // remove attachments bodies from the message body
- $part->body = substr_replace($part->body, "", $startpos, $endpos+strlen($uu_endstring)-$startpos);
-
- $uupart = new rcube_message_part;
-
- $uupart->filename = trim($att[1]);
- $uupart->encoding = 'stream';
- $uupart->body = convert_uudecode($filebody);
- $uupart->size = strlen($uupart->body);
- $uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid;
-
- $ctype = rcube_mime::content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
- $uupart->mimetype = $ctype;
- list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype);
-
- $parts[] = $uupart;
- unset($matches[$pid]);
- }
-
- // remove attachments bodies from the message body
- $part->body = preg_replace($uu_regexp, '', $part->body);
- }
-
- return $parts;
- }
-
-
- /**
- * Deprecated methods (to be removed)
- */
-
- public static function unfold_flowed($text)
- {
- return rcube_mime::unfold_flowed($text);
- }
-
- public static function format_flowed($text, $length = 72)
- {
- return rcube_mime::format_flowed($text, $length);
- }
-
-}
diff --git a/program/include/rcube_message_header.php b/program/include/rcube_message_header.php
deleted file mode 100644
index 378fb98c9..000000000
--- a/program/include/rcube_message_header.php
+++ /dev/null
@@ -1,287 +0,0 @@
-<?php
-
-/**
- +-----------------------------------------------------------------------+
- | program/include/rcube_message_header.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | |
- | 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 Mail
- * @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;
-
- /**
- * 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',
- 'content-transfer-encoding' => 'encoding',
- 'in-reply-to' => 'in_reply_to',
- 'content-type' => 'ctype',
- '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];
- }
-
- return $decode ? rcube_mime::decode_header($value, $this->charset) : $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;
- }
- }
-}
-
-
-/**
- * Class for sorting an array of rcube_message_header objects in a predetermined order.
- *
- * @package Mail
- * @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;
- }
-}
diff --git a/program/include/rcube_message_part.php b/program/include/rcube_message_part.php
deleted file mode 100644
index 80a019e50..000000000
--- a/program/include/rcube_message_part.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_message_part.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | |
- | 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 Mail
- * @author Thomas Bruederli <roundcube@gmail.com>
- * @author Aleksander Machniak <alec@alec.pl>
- * @version 2.0
- */
-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';
-
- public $disposition = '';
- public $filename = '';
- public $encoding = '8bit';
- public $charset = '';
-
- /**
- * Part size in bytes
- *
- * @var int
- */
- public $size = 0;
-
- /**
- * Part headers
- *
- * @var array
- */
- public $headers = array();
-
- 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;
- }
- }
- }
- }
-
-}
diff --git a/program/include/rcube_mime.php b/program/include/rcube_mime.php
deleted file mode 100644
index d8e04a97c..000000000
--- a/program/include/rcube_mime.php
+++ /dev/null
@@ -1,704 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_mime.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | |
- | 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 Mail
- * @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 RCMAIL_CHARSET;
- }
-
-
- /**
- * Parse the given raw message source and return a structure
- * of rcube_message_part objects.
- *
- * It makes use of the PEAR:Mail_mimeDecode library
- *
- * @param string The message source
- * @return object rcube_message_part The message structure
- */
- public static function parse_message($raw_body)
- {
- $mime = new Mail_mimeDecode($raw_body);
- $struct = $mime->decode(array('include_bodies' => true, 'decode_bodies' => true));
- return self::structure_part($struct);
- }
-
-
- /**
- * Recursive method to convert a Mail_mimeDecode part into a rcube_message_part object
- *
- * @param object A message part struct
- * @param int Part count
- * @param string Parent MIME ID
- *
- * @return object rcube_message_part
- */
- private static function structure_part($part, $count=0, $parent='')
- {
- $struct = new rcube_message_part;
- $struct->mime_id = $part->mime_id ? $part->mime_id : (empty($parent) ? (string)$count : "$parent.$count");
- $struct->headers = $part->headers;
- $struct->ctype_primary = $part->ctype_primary;
- $struct->ctype_secondary = $part->ctype_secondary;
- $struct->mimetype = $part->ctype_primary . '/' . $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 ? $struct->charset : self::get_charset();
-
- // determine filename
- if (($filename = $part->d_parameters['filename']) || ($filename = $part->ctype_parameters['name'])) {
- $struct->filename = rcube_mime::decode_mime_string($filename, $part_charset);
- }
-
- // copy part body and convert it to UTF-8 if necessary
- $struct->body = $part->ctype_primary == 'text' || !$part->ctype_parameters['charset'] ? rcube_charset::convert($part->body, $part_charset) : $part->body;
- $struct->size = strlen($part->body);
- $struct->disposition = $part->disposition;
-
- foreach ((array)$part->parts as $child_part) {
- $struct->parts[] = self::structure_part($child_part, ++$count, $struct->mime_id);
- }
-
- return $struct;
- }
-
-
- /**
- * 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
- *
- * @return array Indexed list of addresses
- */
- static function decode_address_list($input, $max = null, $decode = true, $fallback = null)
- {
- $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']);
- $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 = !empty($fallback) ? $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
- * @access private
- */
- public static function parse_headers($headers)
- {
- $a_headers = array();
- $headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers);
- $lines = explode("\n", $headers);
- $c = count($lines);
-
- for ($i=0; $i<$c; $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;
- }
-
-
- /**
- * @access private
- */
- 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 = '';
- }
- 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);
- }
- }
-
- if (!$address && $name) {
- $address = $name;
- }
-
- if ($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
- *
- * @return string Interpreted text with unwrapped lines and stuffed space removed
- */
- public static function unfold_flowed($text)
- {
- $text = preg_split('/\r?\n/', $text);
- $last = -1;
- $q_level = 0;
-
- foreach ($text as $idx => $line) {
- if ($line[0] == '>' && preg_match('/^(>+\s*)/', $line, $regs)) {
- $q = strlen(str_replace(' ', '', $regs[0]));
- $line = substr($line, strlen($regs[0]));
-
- if ($q == $q_level && $line
- && isset($text[$last])
- && $text[$last][strlen($text[$last])-1] == ' '
- ) {
- $text[$last] .= $line;
- unset($text[$idx]);
- }
- else {
- $last = $idx;
- }
- }
- else {
- $q = 0;
- if ($line == '-- ') {
- $last = $idx;
- }
- else {
- // remove space-stuffing
- $line = preg_replace('/^\s/', '', $line);
-
- if (isset($text[$last]) && $line
- && $text[$last] != '-- '
- && $text[$last][strlen($text[$last])-1] == ' '
- ) {
- $text[$last] .= $line;
- unset($text[$idx]);
- }
- else {
- $text[$idx] = $line;
- $last = $idx;
- }
- }
- }
- $q_level = $q;
- }
-
- return implode("\r\n", $text);
- }
-
-
- /**
- * Wrap the given text to comply with RFC 2646
- *
- * @param string $text Text to wrap
- * @param int $length Length
- *
- * @return string Wrapped text
- */
- public static function format_flowed($text, $length = 72)
- {
- $text = preg_split('/\r?\n/', $text);
-
- foreach ($text as $idx => $line) {
- if ($line != '-- ') {
- if ($line[0] == '>' && preg_match('/^(>+)/', $line, $regs)) {
- $prefix = $regs[0];
- $level = strlen($prefix);
- $line = rtrim(substr($line, $level));
- $line = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix ");
- }
- else if ($line) {
- $line = self::wordwrap(rtrim($line), $length - 2, " \r\n");
- // space-stuffing
- $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
- }
-
- $text[$idx] = $line;
- }
- }
-
- return implode("\r\n", $text);
- }
-
-
- /**
- * Improved wordwrap function.
- *
- * @param string $string Text to wrap
- * @param int $width Line width
- * @param string $break Line separator
- * @param bool $cut Enable to cut word
- *
- * @return string Text
- */
- public static function wordwrap($string, $width=75, $break="\n", $cut=false)
- {
- $para = explode($break, $string);
- $string = '';
-
- while (count($para)) {
- $line = array_shift($para);
- if ($line[0] == '>') {
- $string .= $line.$break;
- continue;
- }
-
- $list = explode(' ', $line);
- $len = 0;
- while (count($list)) {
- $line = array_shift($list);
- $l = mb_strlen($line);
- $newlen = $len + $l + ($len ? 1 : 0);
-
- if ($newlen <= $width) {
- $string .= ($len ? ' ' : '').$line;
- $len += (1 + $l);
- }
- else {
- if ($l > $width) {
- if ($cut) {
- $start = 0;
- while ($l) {
- $str = mb_substr($line, $start, $width);
- $strlen = mb_strlen($str);
- $string .= ($len ? $break : '').$str;
- $start += $strlen;
- $l -= $strlen;
- $len = $strlen;
- }
- }
- else {
- $string .= ($len ? $break : '').$line;
- if (count($list)) {
- $string .= $break;
- }
- $len = 0;
- }
- }
- else {
- $string .= $break.$line;
- $len = $l;
- }
- }
- }
-
- if (count($para)) {
- $string .= $break;
- }
- }
-
- return $string;
- }
-
-
- /**
- * A method to guess the mime_type of an attachment.
- *
- * @param string $path Path to the file.
- * @param string $name File name (with suffix)
- * @param string $failover Mime type supplied for failover.
- * @param string $is_stream Set to True if $path contains file body
- *
- * @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)
- {
- $mime_type = null;
- $mime_magic = rcube::get_instance()->config->get('mime_magic');
- $mime_ext = @include RCMAIL_CONFIG_DIR . '/mimetypes.php';
-
- // use file name suffix with hard-coded mime-type map
- if (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')) {
- if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) {
- 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;
- }
-
-
- /**
- * 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;
- }
-
-}
diff --git a/program/include/rcube_output.php b/program/include/rcube_output.php
deleted file mode 100644
index 5c582e67c..000000000
--- a/program/include/rcube_output.php
+++ /dev/null
@@ -1,283 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_output.php |
- | |
- | This file is part of the Roundcube PHP suite |
- | Copyright (C) 2005-2012 The Roundcube Dev Team |
- | |
- | Licensed under the GNU General Public License version 3 or |
- | any later version with exceptions for skins & plugins. |
- | See the README file for a full license statement. |
- | CONTENTS: |
- | Abstract class for output generation |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- | Author: Aleksander Machniak <alec@alec.pl> |
- +-----------------------------------------------------------------------+
-*/
-
-/**
- * Class for output generation
- *
- * @package HTML
- */
-abstract class rcube_output
-{
- public $browser;
- public $type = 'html';
- public $ajax_call = false;
- public $framed = false;
-
- protected $app;
- protected $config;
- protected $charset = RCMAIL_CHARSET;
- protected $env = array();
- protected $pagetitle = '';
- protected $object_handlers = array();
-
-
- /**
- * Object constructor
- */
- public function __construct($task = null, $framed = false)
- {
- $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 $env
- if ($var == 'env')
- return $this->env;
-
- return null;
- }
-
- /**
- * Setter for page title
- *
- * @param string $title Page title
- */
- public function set_pagetitle($title)
- {
- $this->pagetitle = $title;
- }
-
-
- /**
- * 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;
- }
-
-
- /**
- * Getter for the current skin path property
- */
- public function get_skin_path()
- {
- return $this->config->get('skin_path');
- }
-
-
- /**
- * 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();
- $this->object_handlers = array();
- $this->pagetitle = '';
- }
-
-
- /**
- * Call a client method
- *
- * @param string Method to call
- * @param ... Additional arguments
- */
- abstract function command();
-
-
- /**
- * Add a localized label to the client environment
- */
- abstract function add_label();
-
-
- /**
- * 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();
-
-
- /**
- * Register a template object handler
- *
- * @param string Object name
- * @param string Function name to call
- * @return void
- */
- public function add_handler($obj, $func)
- {
- $this->object_handlers[$obj] = $func;
- }
-
-
- /**
- * Register a list of template object handlers
- *
- * @param array Hash array with object=>handler pairs
- * @return void
- */
- public function add_handlers($arr)
- {
- $this->object_handlers = array_merge($this->object_handlers, $arr);
- }
-
-
- /**
- * 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");
-
- // Request browser to disable DNS prefetching (CVE-2010-0464)
- header("X-DNS-Prefetch-Control: off");
-
- // 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, 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: ");
- }
-
-
- /**
- * 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);
- }
-
-
- /**
- * Convert a variable into a javascript object notation
- *
- * @param mixed Input value
- *
- * @return string Serialized JSON string
- */
- public static function json_serialize($input)
- {
- $input = rcube_charset::clean($input);
-
- // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences
- // that's why we have @ here
- return @json_encode($input);
- }
-
-}
diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php
deleted file mode 100644
index 45088850a..000000000
--- a/program/include/rcube_plugin.php
+++ /dev/null
@@ -1,359 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_plugin.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2009, 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 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;
-
- protected $home;
- protected $urlbase;
- private $mytask;
-
-
- /**
- * Default constructor.
- *
- * @param rcube_plugin_api $api Plugin API
- */
- public function __construct($api)
- {
- $this->ID = get_class($this);
- $this->api = $api;
- $this->home = $api->dir . $this->ID;
- $this->urlbase = $api->url . $this->ID . '/';
- }
-
- /**
- * Initialization method, needs to be implemented by the plugin itself
- */
- abstract function init();
-
-
- /**
- * Attempt to load the given plugin which is required for the current plugin
- *
- * @param string Plugin name
- * @return boolean True on success, false on failure
- */
- public function require_plugin($plugin_name)
- {
- return $this->api->load_plugin($plugin_name);
- }
-
-
- /**
- * Load local config file from plugins directory.
- * The loaded values are patched over the global configuration.
- *
- * @param string $fname Config file name relative to the plugin's folder
- * @return boolean True on success, false on failure
- */
- public function load_config($fname = 'config.inc.php')
- {
- $fpath = $this->home.'/'.$fname;
- $rcube = rcube::get_instance();
- if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
- rcube::raise_error(array(
- 'code' => 527, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Failed to load config from $fpath"), true, false);
- return false;
- }
-
- return true;
- }
-
- /**
- * 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;
-
- $rcmail = rcube::get_instance();
- $rcmail->load_language($lang, $add);
-
- // add labels to client
- if ($add2client) {
- $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add);
- $rcmail->output->add_label($js_labels);
- }
- }
- }
-
- /**
- * Wrapper for rcmail::gettext() adding the plugin ID as domain
- *
- * @param string $p Message identifier
- * @return string Localized text
- * @see rcmail::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()
- {
- $rcmail = rcube::get_instance();
- foreach (array($rcmail->config->get('skin'), 'larry') 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)
- {
- return $this->ID.'.'.$key;
- }
-
-}
diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php
deleted file mode 100644
index 107c81026..000000000
--- a/program/include/rcube_plugin_api.php
+++ /dev/null
@@ -1,494 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_plugin_api.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-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: |
- | Plugins repository |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-*/
-
-// location where plugins are loade from
-if (!defined('RCMAIL_PLUGINS_DIR'))
- define('RCMAIL_PLUGINS_DIR', INSTALL_PATH . 'plugins/');
-
-
-/**
- * The plugin loader and global API
- *
- * @package PluginAPI
- */
-class rcube_plugin_api
-{
- static protected $instance;
-
- public $dir;
- public $url = 'plugins/';
- public $task = '';
- public $output;
-
- public $handlers = array();
- protected $plugins = array();
- protected $tasks = array();
- protected $actions = array();
- protected $actionmap = array();
- protected $objectsmap = array();
- protected $template_contents = array();
- protected $active_hook = false;
-
- // Deprecated names of hooks, will be removed after 0.5-stable release
- protected $deprecated_hooks = array(
- 'create_user' => 'user_create',
- 'kill_session' => 'session_destroy',
- 'upload_attachment' => 'attachment_upload',
- 'save_attachment' => 'attachment_save',
- 'get_attachment' => 'attachment_get',
- 'cleanup_attachments' => 'attachments_cleanup',
- 'display_attachment' => 'attachment_display',
- 'remove_attachment' => 'attachment_delete',
- 'outgoing_message_headers' => 'message_outgoing_headers',
- 'outgoing_message_body' => 'message_outgoing_body',
- 'address_sources' => 'addressbooks_list',
- 'get_address_book' => 'addressbook_get',
- 'create_contact' => 'contact_create',
- 'save_contact' => 'contact_update',
- 'contact_save' => 'contact_update',
- 'delete_contact' => 'contact_delete',
- 'manage_folders' => 'folders_list',
- 'list_mailboxes' => 'mailboxes_list',
- 'save_preferences' => 'preferences_save',
- 'user_preferences' => 'preferences_list',
- 'list_prefs_sections' => 'preferences_sections_list',
- 'list_identities' => 'identities_list',
- 'create_identity' => 'identity_create',
- 'delete_identity' => 'identity_delete',
- 'save_identity' => 'identity_update',
- 'identity_save' => 'identity_update',
- // to be removed after 0.8
- 'imap_init' => 'storage_init',
- 'mailboxes_list' => 'storage_folders',
- );
-
- /**
- * This implements the 'singleton' design pattern
- *
- * @return rcube_plugin_api The one and only instance if this class
- */
- static function get_instance()
- {
- if (!self::$instance) {
- self::$instance = new rcube_plugin_api();
- }
-
- return self::$instance;
- }
-
-
- /**
- * Private constructor
- */
- protected function __construct()
- {
- $this->dir = slashify(RCMAIL_PLUGINS_DIR);
- }
-
-
- /**
- * Initialize plugin engine
- *
- * This has to be done after rcmail::load_gui() or rcmail::json_init()
- * was called because plugins need to have access to rcmail->output
- *
- * @param object rcube Instance of the rcube base class
- * @param string Current application task (used for conditional plugin loading)
- */
- public function init($app, $task = '')
- {
- $this->task = $task;
- $this->output = $app->output;
-
- // register an internal hook
- $this->register_hook('template_container', array($this, 'template_container_hook'));
-
- // maybe also register a shudown function which triggers shutdown functions of all plugin objects
- }
-
-
- /**
- * Load and init all enabled plugins
- *
- * This has to be done after rcmail::load_gui() or rcmail::json_init()
- * was called because plugins need to have access to rcmail->output
- *
- * @param array List of configured plugins to load
- * @param array List of plugins required by the application
- */
- public function load_plugins($plugins_enabled, $required_plugins = array())
- {
- foreach ($plugins_enabled as $plugin_name) {
- $this->load_plugin($plugin_name);
- }
-
- // 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
- * @return boolean True on success, false if not loaded or failure
- */
- public function load_plugin($plugin_name)
- {
- static $plugins_dir;
-
- if (!$plugins_dir) {
- $dir = dir($this->dir);
- $plugins_dir = unslashify($dir->path);
- }
-
- // plugin already loaded
- if ($this->plugins[$plugin_name] || class_exists($plugin_name, false))
- return true;
-
- $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
-
- if (file_exists($fn)) {
- include($fn);
-
- // instantiate class if exists
- if (class_exists($plugin_name, false)) {
- $plugin = new $plugin_name($this);
- // check inheritance...
- if (is_subclass_of($plugin, 'rcube_plugin')) {
- // ... task, request type and framed mode
- if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
- && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
- && (!$plugin->noframe || empty($_REQUEST['_framed']))
- ) {
- $plugin->init();
- $this->plugins[$plugin_name] = $plugin;
- }
- return true;
- }
- }
- else {
- rcube::raise_error(array('code' => 520, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "No plugin class $plugin_name found in $fn"), true, false);
- }
- }
- else {
- rcube::raise_error(array('code' => 520, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Failed to load plugin file $fn"), true, false);
- }
-
- return false;
- }
-
-
- /**
- * 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, $this->handlers[$hook]);
- if ($callback_id !== false) {
- unset($this->handlers[$hook][$callback_id]);
- }
- }
-
-
- /**
- * Triggers a plugin hook.
- * This is called from the application and executes all registered handlers
- *
- * @param string $hook Hook name
- * @param array $args Named arguments (key->value pairs)
- * @return array The (probably) altered hook arguments
- */
- public function exec_hook($hook, $args = array())
- {
- if (!is_array($args))
- $args = array('arg' => $args);
-
- $args += array('abort' => false);
- $this->active_hook = $hook;
-
- foreach ((array)$this->handlers[$hook] as $callback) {
- $ret = call_user_func($callback, $args);
- if ($ret && is_array($ret))
- $args = $ret + $args;
-
- if ($args['abort'])
- break;
- }
-
- $this->active_hook = false;
- return $args;
- }
-
-
- /**
- * Let a plugin register a handler for a specific request
- *
- * @param string $action Action name (_task=mail&_action=plugin.foo)
- * @param string $owner Plugin name that registers this action
- * @param mixed $callback Callback: string with global function name or array($obj, 'methodname')
- * @param string $task Task name registered by this plugin
- */
- public function register_action($action, $owner, $callback, $task = null)
- {
- // check action name
- if ($task)
- $action = $task.'.'.$action;
- else if (strpos($action, 'plugin.') !== 0)
- $action = 'plugin.'.$action;
-
- // can register action only if it's not taken or registered by myself
- if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
- $this->actions[$action] = $callback;
- $this->actionmap[$action] = $owner;
- }
- 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 {
- 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)
- {
- if ($task != asciiwords($task)) {
- rcube::raise_error(array('code' => 526, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false);
- }
- else if (in_array($task, rcmail::$main_tasks)) {
- rcube::raise_error(array('code' => 526, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false);
- }
- else {
- $this->tasks[$task] = $owner;
- rcmail::$main_tasks[] = $task;
- return true;
- }
-
- return false;
- }
-
-
- /**
- * Checks whether the given task is registered by a plugin
- *
- * @param string $task Task name
- * @return boolean True if registered, otherwise false
- */
- public function is_plugin_task($task)
- {
- return $this->tasks[$task] ? true : false;
- }
-
-
- /**
- * Check if a plugin hook is currently processing.
- * Mainly used to prevent loops and recursion.
- *
- * @param string $hook Hook to check (optional)
- * @return boolean True if any/the given hook is currently processed, otherwise false
- */
- public function is_processing($hook = null)
- {
- return $this->active_hook && (!$hook || $this->active_hook == $hook);
- }
-
- /**
- * Include a plugin script file in the current HTML page
- *
- * @param string $fn Path to script
- */
- public function include_script($fn)
- {
- if (is_object($this->output) && $this->output->type == 'html') {
- $src = $this->resource_url($fn);
- $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
- }
- }
-
-
- /**
- * 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);
- }
-
-
- /**
- * 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;
- }
-
-}
diff --git a/program/include/rcube_result_index.php b/program/include/rcube_result_index.php
deleted file mode 100644
index 334ec8530..000000000
--- a/program/include/rcube_result_index.php
+++ /dev/null
@@ -1,450 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_result_index.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2011, The Roundcube Dev Team |
- | Copyright (C) 2011, Kolab Systems AG |
- | |
- | 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
- */
-class rcube_result_index
-{
- 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)
- {
- $this->mailbox = $mailbox;
- $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 ? true : false;
- }
-
-
- /**
- * 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) ? true : false;
- }
-
-
- /**
- * 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 listed in $ids list.
- *
- * @param array $ids List of IDs to remove.
- */
- public function filter($ids = array())
- {
- $data = $this->get();
- $data = array_diff($data, $ids);
-
- $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 keep.
- */
- public function intersect($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;
- }
-
- // @TODO: maybe do this in chunks
- $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'];
- }
-}
diff --git a/program/include/rcube_result_thread.php b/program/include/rcube_result_thread.php
deleted file mode 100644
index b2325a499..000000000
--- a/program/include/rcube_result_thread.php
+++ /dev/null
@@ -1,673 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_result_thread.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2011, The Roundcube Dev Team |
- | Copyright (C) 2011, Kolab Systems AG |
- | |
- | 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
- */
-class rcube_result_thread
-{
- 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 ? true : false;
- }
-
-
- /**
- * 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) ? true : false;
- }
-
-
- /**
- * 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;
- }
-
- $this->meta['pos'] = array();
- $datalen = strlen($this->raw_data);
- $result = '';
- $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;
-
- $result = $elem . self::SEPARATOR_ELEMENT . $result;
- }
-
- $this->raw_data = rtrim($result, self::SEPARATOR_ELEMENT);
- }
-
-
- /**
- * 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 = $this->params;
- $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->intersect($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] != '(') {
- $stop = $begin + strspn($str, '1234567890', $begin, $end - $begin);
- $msg = substr($str, $begin, $stop - $begin);
- if (!$msg) {
- return $node;
- }
-
- $this->meta['messages']++;
-
- $node .= ($depth ? self::SEPARATOR_ITEM.$depth.self::SEPARATOR_LEVEL : '').$msg;
-
- if ($stop + 1 < $end) {
- $node .= $this->parse_thread($str, $stop + 1, $end, $depth + 1);
- }
- } 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;
- }
-}
diff --git a/program/include/rcube_session.php b/program/include/rcube_session.php
deleted file mode 100644
index 6192466cd..000000000
--- a/program/include/rcube_session.php
+++ /dev/null
@@ -1,649 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_session.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2011, Kolab Systems AG |
- | |
- | 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> |
- +-----------------------------------------------------------------------+
-*/
-
-/**
- * Class to provide database supported session storage
- *
- * @package Core
- * @author Thomas Bruederli <roundcube@gmail.com>
- * @author Aleksander Machniak <alec@alec.pl>
- */
-class rcube_session
-{
- private $db;
- private $ip;
- private $start;
- private $changed;
- private $unsets = array();
- private $gc_handlers = array();
- private $cookiename = 'roundcube_sessauth';
- private $vars;
- private $key;
- private $now;
- private $secret = '';
- private $ip_check = false;
- private $logging = false;
- private $keep_alive = 0;
- private $memcache;
-
- /**
- * Default constructor
- */
- public function __construct($db, $config)
- {
- $this->db = $db;
- $this->start = microtime(true);
- $this->ip = $_SERVER['REMOTE_ADDR'];
- $this->logging = $config->get('log_session', false);
-
- $lifetime = $config->get('session_lifetime', 1) * 60;
- $this->set_lifetime($lifetime);
-
- // use memcache backend
- if ($config->get('session_storage', 'db') == 'memcache') {
- $this->memcache = rcube::get_instance()->get_memcache();
-
- // set custom functions for PHP session management if memcache is available
- if ($this->memcache) {
- session_set_save_handler(
- array($this, 'open'),
- array($this, 'close'),
- array($this, 'mc_read'),
- array($this, 'mc_write'),
- array($this, 'mc_destroy'),
- array($this, 'gc'));
- }
- else {
- rcube::raise_error(array('code' => 604, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => "Failed to connect to memcached. Please check configuration"),
- true, true);
- }
- }
- else {
- // set custom functions for PHP session management
- session_set_save_handler(
- array($this, 'open'),
- array($this, 'close'),
- array($this, 'db_read'),
- array($this, 'db_write'),
- array($this, 'db_destroy'),
- array($this, 'db_gc'));
- }
- }
-
-
- public function open($save_path, $session_name)
- {
- return true;
- }
-
-
- public function close()
- {
- return true;
- }
-
-
- /**
- * Delete session data for the given key
- *
- * @param string Session ID
- */
- public function destroy($key)
- {
- return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key);
- }
-
-
- /**
- * Read session data from database
- *
- * @param string Session ID
- * @return string Session vars
- */
- public function db_read($key)
- {
- $sql_result = $this->db->query(
- "SELECT vars, ip, changed FROM ".$this->db->table_name('session')
- ." WHERE sess_id = ?", $key);
-
- if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
- $this->changed = strtotime($sql_arr['changed']);
- $this->ip = $sql_arr['ip'];
- $this->vars = base64_decode($sql_arr['vars']);
- $this->key = $key;
-
- return !empty($this->vars) ? (string) $this->vars : '';
- }
-
- return null;
- }
-
-
- /**
- * Save session data.
- * handler for session_read()
- *
- * @param string Session ID
- * @param string Serialized session vars
- * @return boolean True on success
- */
- public function db_write($key, $vars)
- {
- $ts = microtime(true);
- $now = $this->db->fromunixtime((int)$ts);
-
- // no session row in DB (db_read() returns false)
- if (!$this->key) {
- $oldvars = null;
- }
- // use internal data from read() for fast requests (up to 0.5 sec.)
- else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) {
- $oldvars = $this->vars;
- }
- else { // else read data again from DB
- $oldvars = $this->db_read($key);
- }
-
- if ($oldvars !== null) {
- $newvars = $this->_fixvars($vars, $oldvars);
-
- if ($newvars !== $oldvars) {
- $this->db->query(
- sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?",
- $this->db->table_name('session'), $now),
- base64_encode($newvars), $key);
- }
- else if ($ts - $this->changed > $this->lifetime / 2) {
- $this->db->query("UPDATE ".$this->db->table_name('session')." SET changed=$now WHERE sess_id=?", $key);
- }
- }
- else {
- $this->db->query(
- sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ".
- "VALUES (?, ?, ?, %s, %s)",
- $this->db->table_name('session'), $now, $now),
- $key, base64_encode($vars), (string)$this->ip);
- }
-
- return true;
- }
-
-
- /**
- * Merge vars with old vars and apply unsets
- */
- private function _fixvars($vars, $oldvars)
- {
- if ($oldvars !== null) {
- $a_oldvars = $this->unserialize($oldvars);
- if (is_array($a_oldvars)) {
- foreach ((array)$this->unsets as $k)
- unset($a_oldvars[$k]);
-
- $newvars = $this->serialize(array_merge(
- (array)$a_oldvars, (array)$this->unserialize($vars)));
- }
- else
- $newvars = $vars;
- }
-
- $this->unsets = array();
- return $newvars;
- }
-
-
- /**
- * Handler for session_destroy()
- *
- * @param string Session ID
- *
- * @return boolean True on success
- */
- public function db_destroy($key)
- {
- if ($key) {
- $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?", $this->db->table_name('session')), $key);
- }
-
- return true;
- }
-
-
- /**
- * Garbage collecting function
- *
- * @param string Session lifetime in seconds
- * @return boolean True on success
- */
- public function db_gc($maxlifetime)
- {
- // just delete all expired sessions
- $this->db->query(
- sprintf("DELETE FROM %s WHERE changed < %s",
- $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
-
- $this->gc();
-
- return true;
- }
-
-
- /**
- * Read session data from memcache
- *
- * @param string Session ID
- * @return string Session vars
- */
- public function mc_read($key)
- {
- if ($value = $this->memcache->get($key)) {
- $arr = unserialize($value);
- $this->changed = $arr['changed'];
- $this->ip = $arr['ip'];
- $this->vars = $arr['vars'];
- $this->key = $key;
-
- return !empty($this->vars) ? (string) $this->vars : '';
- }
-
- return null;
- }
-
-
- /**
- * Save session data.
- * handler for session_read()
- *
- * @param string Session ID
- * @param string Serialized session vars
- * @return boolean True on success
- */
- public function mc_write($key, $vars)
- {
- $ts = microtime(true);
-
- // no session data in cache (mc_read() returns false)
- if (!$this->key)
- $oldvars = null;
- // use internal data for fast requests (up to 0.5 sec.)
- else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5))
- $oldvars = $this->vars;
- else // else read data again
- $oldvars = $this->mc_read($key);
-
- $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars;
-
- if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2)
- return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), MEMCACHE_COMPRESSED, $this->lifetime);
-
- return true;
- }
-
-
- /**
- * Handler for session_destroy() with memcache backend
- *
- * @param string Session ID
- *
- * @return boolean True on success
- */
- public function mc_destroy($key)
- {
- if ($key) {
- // #1488592: use 2nd argument
- $this->memcache->delete($key, 0);
- }
-
- return true;
- }
-
-
- /**
- * Execute registered garbage collector routines
- */
- public function gc()
- {
- foreach ($this->gc_handlers as $fct) {
- call_user_func($fct);
- }
- }
-
-
- /**
- * 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;
- }
-
-
- /**
- * Generate and set new session id
- *
- * @param boolean $destroy If enabled the current session will be destroyed
- */
- public function regenerate_id($destroy=true)
- {
- session_regenerate_id($destroy);
-
- $this->vars = null;
- $this->key = session_id();
-
- return true;
- }
-
-
- /**
- * Unset a session variable
- *
- * @param string Varibale name
- * @return boolean True on success
- */
- public function remove($var=null)
- {
- if (empty($var))
- return $this->destroy(session_id());
-
- $this->unsets[] = $var;
- unset($_SESSION[$var]);
-
- return true;
- }
-
-
- /**
- * Kill this session
- */
- public function kill()
- {
- $this->vars = null;
- $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed)
- $this->destroy(session_id());
- rcube_utils::setcookie($this->cookiename, '-del-', time() - 60);
- }
-
-
- /**
- * Re-read session data from storage backend
- */
- public function reload()
- {
- if ($this->key && $this->memcache)
- $data = $this->mc_read($this->key);
- else if ($this->key)
- $data = $this->db_read($this->key);
-
- if ($data)
- session_decode($data);
- }
-
-
- /**
- * Serialize session data
- */
- private function serialize($vars)
- {
- $data = '';
- if (is_array($vars))
- foreach ($vars as $var=>$value)
- $data .= $var.'|'.serialize($value);
- else
- $data = 'b:0;';
- return $data;
- }
-
-
- /**
- * Unserialize session data
- * http://www.php.net/manual/en/function.session-decode.php#56106
- */
- private function unserialize($str)
- {
- $str = (string)$str;
- $endptr = strlen($str);
- $p = 0;
-
- $serialized = '';
- $items = 0;
- $level = 0;
-
- while ($p < $endptr) {
- $q = $p;
- while ($str[$q] != '|')
- if (++$q >= $endptr) break 2;
-
- if ($str[$p] == '!') {
- $p++;
- $has_value = false;
- } else {
- $has_value = true;
- }
-
- $name = substr($str, $p, $q - $p);
- $q++;
-
- $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
-
- if ($has_value) {
- for (;;) {
- $p = $q;
- switch (strtolower($str[$q])) {
- case 'n': /* null */
- case 'b': /* boolean */
- case 'i': /* integer */
- case 'd': /* decimal */
- do $q++;
- while ( ($q < $endptr) && ($str[$q] != ';') );
- $q++;
- $serialized .= substr($str, $p, $q - $p);
- if ($level == 0) break 2;
- break;
- case 'r': /* reference */
- $q+= 2;
- for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q];
- $q++;
- $serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */
- if ($level == 0) break 2;
- break;
- case 's': /* string */
- $q+=2;
- for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q];
- $q+=2;
- $q+= (int)$length + 2;
- $serialized .= substr($str, $p, $q - $p);
- if ($level == 0) break 2;
- break;
- case 'a': /* array */
- case 'o': /* object */
- do $q++;
- while ( ($q < $endptr) && ($str[$q] != '{') );
- $q++;
- $level++;
- $serialized .= substr($str, $p, $q - $p);
- break;
- case '}': /* end of array|object */
- $q++;
- $serialized .= substr($str, $p, $q - $p);
- if (--$level == 0) break 2;
- break;
- default:
- return false;
- }
- }
- } 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));
- }
-
- /**
- * Setter for keep_alive interval
- */
- public function set_keep_alive($keep_alive)
- {
- $this->keep_alive = $keep_alive;
-
- if ($this->lifetime < $keep_alive)
- $this->set_lifetime($keep_alive + 30);
- }
-
- /**
- * Getter for keep_alive interval
- */
- public function get_keep_alive()
- {
- return $this->keep_alive;
- }
-
- /**
- * Getter for remote IP saved with this session
- */
- public function get_ip()
- {
- return $this->ip;
- }
-
- /**
- * Setter for cookie encryption secret
- */
- function set_secret($secret)
- {
- $this->secret = $secret;
- }
-
-
- /**
- * Enable/disable IP check
- */
- function set_ip_check($check)
- {
- $this->ip_check = $check;
- }
-
-
- /**
- * Setter for the cookie name used for session cookie
- */
- function set_cookiename($cookiename)
- {
- if ($cookiename)
- $this->cookiename = $cookiename;
- }
-
-
- /**
- * Check session authentication cookie
- *
- * @return boolean True if valid, False if not
- */
- function check_auth()
- {
- $this->cookie = $_COOKIE[$this->cookiename];
- $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true;
-
- if (!$result)
- $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']);
-
- if ($result && $this->_mkcookie($this->now) != $this->cookie) {
- $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now));
- $result = false;
-
- // Check if using id from a previous time slot
- for ($i = 1; $i <= 2; $i++) {
- $prev = $this->now - ($this->lifetime / 2) * $i;
- if ($this->_mkcookie($prev) == $this->cookie) {
- $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie);
- $this->set_auth_cookie();
- $result = true;
- }
- }
- }
-
- if (!$result)
- $this->log("Session authentication failed for " . $this->key . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev));
-
- return $result;
- }
-
-
- /**
- * Set session authentication cookie
- */
- function set_auth_cookie()
- {
- $this->cookie = $this->_mkcookie($this->now);
- rcube_utils::setcookie($this->cookiename, $this->cookie, 0);
- $_COOKIE[$this->cookiename] = $this->cookie;
- }
-
-
- /**
- * Create session cookie from session data
- *
- * @param int Time slot to use
- */
- function _mkcookie($timeslot)
- {
- $auth_string = "$this->key,$this->secret,$timeslot";
- return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
- }
-
- /**
- * Writes debug information to the log
- */
- function log($line)
- {
- if ($this->logging)
- rcube::write_log('session', $line);
- }
-
-}
diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc
deleted file mode 100644
index 4577c6df5..000000000
--- a/program/include/rcube_shared.inc
+++ /dev/null
@@ -1,474 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_shared.inc |
- | |
- | This file is part of the Roundcube PHP suite |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | |
- | Licensed under the GNU General Public License version 3 or |
- | any later version with exceptions for skins & plugins. |
- | See the README file for a full license statement. |
- | |
- | CONTENTS: |
- | Shared functions used by Roundcube Framework |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Roundcube shared functions
- *
- * @package Core
- */
-
-
-/**
- * Similar function as in_array() but case-insensitive
- *
- * @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)
-{
- $needle = mb_strtolower($needle);
- foreach ((array)$haystack as $value) {
- if ($needle === mb_strtolower($value)) {
- return true;
- }
- }
-
- return false;
-}
-
-
-/**
- * Find out if the string content means true or false
- *
- * @param string $str Input value
- *
- * @return boolean Boolean value
- */
-function get_boolean($str)
-{
- $str = strtolower($str);
-
- return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true);
-}
-
-
-/**
- * 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);
-}
-
-
-/**
- * Delete all files within a folder
- *
- * @param string Path to directory
- *
- * @return boolean True on success, False if directory was not found
- */
-function clear_directory($dir_path)
-{
- $dir = @opendir($dir_path);
- if (!$dir) {
- return false;
- }
-
- while ($file = readdir($dir)) {
- if (strlen($file) > 2) {
- unlink("$dir_path/$file");
- }
- }
-
- closedir($dir);
-
- return true;
-}
-
-
-/**
- * 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;
-
- $str = mb_substr($str, 0, $first_part_length) . $placeholder . mb_substr($str, $second_starting_location);
- }
-
- 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;
-}
-
-
-/**
- * Remove single and double quotes from a given string
- *
- * @param string Input value
- *
- * @return string Dequoted string
- */
-function strip_quotes($str)
-{
- return str_replace(array("'", '"'), '', $str);
-}
-
-
-/**
- * Remove new lines characters from given string
- *
- * @param string $str Input value
- *
- * @return string Stripped string
- */
-function strip_newlines($str)
-{
- return preg_replace('/[\r\n]/', '', $str);
-}
-
-
-/**
- * Compose a valid representation of name and e-mail address
- *
- * @param string $email E-mail address
- * @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;
-}
-
-
-/**
- * mbstring replacement functions
- */
-if (!extension_loaded('mbstring'))
-{
- function mb_strlen($str)
- {
- return strlen($str);
- }
-
- function mb_strtolower($str)
- {
- return strtolower($str);
- }
-
- function mb_strtoupper($str)
- {
- return strtoupper($str);
- }
-
- function mb_substr($str, $start, $len=null)
- {
- return substr($str, $start, $len);
- }
-
- function mb_strpos($haystack, $needle, $offset=0)
- {
- return strpos($haystack, $needle, $offset);
- }
-
- function mb_strrpos($haystack, $needle, $offset=0)
- {
- return strrpos($haystack, $needle, $offset);
- }
-}
-
-/**
- * intl replacement functions
- */
-
-if (!function_exists('idn_to_utf8'))
-{
- function idn_to_utf8($domain, $flags=null)
- {
- 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, $flags=null)
- {
- 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)
-{
- $filename = preg_replace(
- array(
- '/Mail_(.+)/',
- '/Net_(.+)/',
- '/Auth_(.+)/',
- '/^html_.+/',
- '/^utf8$/',
- ),
- array(
- 'Mail/\\1',
- 'Net/\\1',
- 'Auth/\\1',
- 'html',
- 'utf8.class',
- ),
- $classname
- );
-
- if ($fp = @fopen("$filename.php", 'r', true)) {
- fclose($fp);
- include_once "$filename.php";
- return true;
- }
-
- return false;
-}
-
-/**
- * Local callback function for PEAR errors
- */
-function rcube_pear_error($err)
-{
- error_log(sprintf("%s (%s): %s",
- $err->getMessage(),
- $err->getCode(),
- $err->getUserinfo()), 0);
-}
diff --git a/program/include/rcube_smtp.php b/program/include/rcube_smtp.php
deleted file mode 100644
index b28be5206..000000000
--- a/program/include/rcube_smtp.php
+++ /dev/null
@@ -1,469 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_smtp.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2010, 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> |
- +-----------------------------------------------------------------------+
-*/
-
-// define headers delimiter
-define('SMTP_MIME_CRLF', "\r\n");
-
-/**
- * Class to provide SMTP functionality using PEAR Net_SMTP
- *
- * @package Mail
- * @author Thomas Bruederli <roundcube@gmail.com>
- * @author Aleksander Machniak <alec@alec.pl>
- */
-class rcube_smtp
-{
-
- private $conn = null;
- private $response;
- private $error;
-
-
- /**
- * SMTP Connection and authentication
- *
- * @param string Server host
- * @param string Server port
- * @param string User name
- * @param string Password
- *
- * @return bool Returns true on success, or false on error
- */
- public function connect($host=null, $port=null, $user=null, $pass=null)
- {
- $rcube = rcube::get_instance();
-
- // disconnect/destroy $this->conn
- $this->disconnect();
-
- // reset error/response var
- $this->error = $this->response = null;
-
- // let plugins alter smtp connection config
- $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array(
- 'smtp_server' => $host ? $host : $rcube->config->get('smtp_server'),
- 'smtp_port' => $port ? $port : $rcube->config->get('smtp_port', 25),
- 'smtp_user' => $user ? $user : $rcube->config->get('smtp_user'),
- 'smtp_pass' => $pass ? $pass : $rcube->config->get('smtp_pass'),
- 'smtp_auth_cid' => $rcube->config->get('smtp_auth_cid'),
- 'smtp_auth_pw' => $rcube->config->get('smtp_auth_pw'),
- 'smtp_auth_type' => $rcube->config->get('smtp_auth_type'),
- 'smtp_helo_host' => $rcube->config->get('smtp_helo_host'),
- 'smtp_timeout' => $rcube->config->get('smtp_timeout'),
- 'smtp_auth_callbacks' => array(),
- ));
-
- $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']);
- // when called from Installer it's possible to have empty $smtp_host here
- if (!$smtp_host) $smtp_host = 'localhost';
- $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
- $smtp_host_url = parse_url($smtp_host);
-
- // overwrite port
- if (isset($smtp_host_url['host']) && isset($smtp_host_url['port']))
- {
- $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;
- }
-
- 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);
-
- if ($rcube->config->get('smtp_debug'))
- $this->conn->setDebug(true, array($this, 'debug_handler'));
-
- // 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($smtp_timeout);
-
- if (PEAR::isError($result)) {
- $this->response[] = "Connection failed: ".$result->getMessage();
- $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code));
- $this->conn = null;
- return false;
- }
-
- // 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', $_SESSION['username'], $CONFIG['smtp_user']);
- $smtp_pass = str_replace('%p', $rcube->decrypt($_SESSION['password']), $CONFIG['smtp_pass']);
- $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type'];
-
- 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 (PEAR::isError($result))
- {
- $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code));
- $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')';
- $this->reset();
- $this->disconnect();
- return false;
- }
- }
-
- return true;
- }
-
-
- /**
- * 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;
- else
- {
- $this->reset();
- $this->response[] = "Invalid message headers";
- return false;
- }
-
- // exit if no from address is given
- if (!isset($from))
- {
- $this->reset();
- $this->response[] = "No From address has been provided";
- return false;
- }
-
- // RFC3461: Delivery Status Notification
- if ($opts['dsn']) {
- $exts = $this->conn->getServiceExtensions();
-
- if (isset($exts['DSN'])) {
- $from_params = 'RET=HDRS';
- $recipient_params = 'NOTIFY=SUCCESS,FAILURE';
- }
- }
-
- // RFC2298.3: remove envelope sender address
- if (preg_match('/Content-Type: multipart\/report/', $text_headers)
- && preg_match('/report-type=disposition-notification/', $text_headers)
- ) {
- $from = '';
- }
-
- // set From: address
- if (PEAR::isError($this->conn->mailFrom($from, $from_params)))
- {
- $err = $this->conn->getResponse();
- $this->error = array('label' => 'smtpfromerror', 'vars' => array(
- 'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1]));
- $this->response[] = "Failed to set sender '$from'";
- $this->reset();
- return false;
- }
-
- // prepare list of recipients
- $recipients = $this->_parse_rfc822($recipients);
- if (PEAR::isError($recipients))
- {
- $this->error = array('label' => 'smtprecipientserror');
- $this->reset();
- return false;
- }
-
- // set mail recipients
- foreach ($recipients as $recipient)
- {
- if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) {
- $err = $this->conn->getResponse();
- $this->error = array('label' => 'smtptoerror', 'vars' => array(
- 'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1]));
- $this->response[] = "Failed to add recipient '$recipient'";
- $this->reset();
- return false;
- }
- }
-
- if (is_resource($body))
- {
- // file handle
- $data = $body;
- $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers);
- } else {
- // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data
- // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy.
- // We are still forced to make another copy here for a couple ticks so we don't really
- // get to save a copy in the method call.
- $data = $text_headers . "\r\n" . $body;
-
- // unset old vars to save data and so we can pass into SMTP_CONN->data by reference.
- unset($text_headers, $body);
- }
-
- // Send the message's headers and the body as SMTP data.
- if (PEAR::isError($result = $this->conn->data($data, $text_headers)))
- {
- $err = $this->conn->getResponse();
- if (!in_array($err[0], array(354, 250, 221)))
- $msg = sprintf('[%d] %s', $err[0], $err[1]);
- else
- $msg = $result->getMessage();
-
- $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg));
- $this->response[] = "Failed to send data";
- $this->reset();
- return false;
- }
-
- $this->response[] = join(': ', $this->conn->getResponse());
- return true;
- }
-
-
- /**
- * Reset the global SMTP connection
- * @access public
- */
- public function reset()
- {
- if (is_object($this->conn))
- $this->conn->rset();
- }
-
-
- /**
- * Disconnect the global SMTP connection
- * @access public
- */
- public function disconnect()
- {
- if (is_object($this->conn)) {
- $this->conn->disconnect();
- $this->conn = null;
- }
- }
-
-
- /**
- * This is our own debug handler for the SMTP connection
- * @access public
- */
- public function debug_handler(&$smtp, $message)
- {
- rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
- }
-
-
- /**
- * Get error message
- * @access public
- */
- public function get_error()
- {
- return $this->error;
- }
-
-
- /**
- * Get server response messages array
- * @access public
- */
- public function get_response()
- {
- return $this->response;
- }
-
-
- /**
- * Take an array of mail headers and return a string containing
- * text usable in sending a message.
- *
- * @param array $headers The array of headers to prepare, in an associative
- * array, where the array key is the header name (ie,
- * 'Subject'), and the array value is the header
- * value (ie, 'test'). The header produced from those
- * values would be 'Subject: test'.
- *
- * @return mixed Returns false if it encounters a bad address,
- * otherwise returns an array containing two
- * elements: Any From: address found in the headers,
- * and the plain text version of the headers.
- * @access private
- */
- private function _prepare_headers($headers)
- {
- $lines = array();
- $from = null;
-
- foreach ($headers as $key => $value)
- {
- 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(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF);
- }
-
- /**
- * Take a set of recipients and parse them, returning an array of
- * bare addresses (forward paths) that can be passed to sendmail
- * or an smtp server with the rcpt to: command.
- *
- * @param mixed Either a comma-seperated list of recipients
- * (RFC822 compliant), or an array of recipients,
- * each RFC822 valid.
- *
- * @return array An array of forward paths (bare addresses).
- * @access private
- */
- private function _parse_rfc822($recipients)
- {
- // if we're passed an array, assume addresses are valid and implode them before parsing.
- if (is_array($recipients))
- $recipients = implode(', ', $recipients);
-
- $addresses = array();
- $recipients = rcube_utils::explode_quoted_string(',', $recipients);
-
- reset($recipients);
- while (list($k, $recipient) = each($recipients))
- {
- $a = rcube_utils::explode_quoted_string(' ', $recipient);
- while (list($k2, $word) = each($a))
- {
- if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"')
- {
- $word = preg_replace('/^<|>$/', '', trim($word));
- if (in_array($word, $addresses)===false)
- array_push($addresses, $word);
- }
- }
- }
-
- return $addresses;
- }
-
-}
diff --git a/program/include/rcube_spellchecker.php b/program/include/rcube_spellchecker.php
deleted file mode 100644
index 219dca780..000000000
--- a/program/include/rcube_spellchecker.php
+++ /dev/null
@@ -1,620 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_spellchecker.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2011, Kolab Systems AG |
- | Copyright (C) 2008-2011, The Roundcube Dev Team |
- | |
- | 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 Core
- */
-class rcube_spellchecker
-{
- private $matches = array();
- private $engine;
- private $lang;
- private $rc;
- private $error;
- private $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.]([^\w]|$)/';
- private $options = array();
- private $dict;
- private $have_dict;
-
-
- // default settings
- const GOOGLE_HOST = 'ssl://www.google.com';
- const GOOGLE_PORT = 443;
- const MAX_SUGGESTIONS = 10;
-
-
- /**
- * 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 ? $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'),
- );
- }
-
-
- /**
- * 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->engine == 'pspell') {
- $this->matches = $this->_pspell_check($this->content);
- }
- else {
- $this->matches = $this->_googie_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->engine == 'pspell') {
- return $this->_pspell_suggestions($word);
- }
-
- return $this->_googie_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
- */
- function get_words($text = null, $is_html=false)
- {
- if ($this->engine == 'pspell') {
- return $this->_pspell_words($text, $is_html);
- }
-
- return $this->_googie_words($text, $is_html);
- }
-
-
- /**
- * Returns checking result in XML (Googiespell) format
- *
- * @return string XML content
- */
- function get_xml()
- {
- // send output
- $out = '<?xml version="1.0" encoding="'.RCMAIL_CHARSET.'"?><spellresult charschecked="'.mb_strlen($this->content).'">';
-
- foreach ($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 ($this->matches as $item) {
- if ($this->engine == 'pspell') {
- $word = $item[0];
- }
- else {
- $word = mb_substr($this->content, $item[1], $item[2], RCMAIL_CHARSET);
- }
- $result[$word] = is_array($item[4]) ? implode("\t", $item[4]) : $item[4];
- }
-
- return $result;
- }
-
-
- /**
- * Returns error message
- *
- * @return string Error message
- */
- function error()
- {
- return $this->error;
- }
-
-
- /**
- * Checks the text using pspell
- *
- * @param string $text Text content for spellchecking
- */
- private function _pspell_check($text)
- {
- // init spellchecker
- $this->_pspell_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->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);
- }
-
- return $matches;
- }
-
-
- /**
- * Returns the misspelled words
- */
- private function _pspell_words($text = null, $is_html=false)
- {
- $result = array();
-
- if ($text) {
- // init spellchecker
- $this->_pspell_init();
-
- if (!$this->plink) {
- return array();
- }
-
- // With PSpell we don't need to get suggestions to return misspelled words
- if ($is_html) {
- $text = $this->html2text($text);
- }
-
- $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
-
- foreach ($text as $w) {
- $word = trim($w[0]);
-
- // skip exceptions
- if ($this->is_exception($word)) {
- continue;
- }
-
- if (!pspell_check($this->plink, $word)) {
- $result[] = $word;
- }
- }
-
- return $result;
- }
-
- foreach ($this->matches as $m) {
- $result[] = $m[0];
- }
-
- return $result;
- }
-
-
- /**
- * Returns suggestions for misspelled word
- */
- private function _pspell_suggestions($word)
- {
- // init spellchecker
- $this->_pspell_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();
- }
-
-
- /**
- * Initializes PSpell dictionary
- */
- private function _pspell_init()
- {
- if (!$this->plink) {
- if (!extension_loaded('pspell')) {
- $this->error = "Pspell extension not available";
- rcube::raise_error(array(
- 'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => $this->error), true, false);
-
- return;
- }
-
- $this->plink = pspell_new($this->lang, null, null, RCMAIL_CHARSET, PSPELL_FAST);
- }
-
- if (!$this->plink) {
- $this->error = "Unable to load Pspell engine for selected language";
- }
- }
-
-
- private function _googie_check($text)
- {
- // spell check uri is configured
- $url = $this->rc->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::GOOGLE_HOST;
- $port = self::GOOGLE_PORT;
- $path = '/tbproxy/spell?lang=' . $this->lang;
- }
-
- // Google has some problem with spaces, use \n instead
- $gtext = str_replace(' ', "\n", $text);
-
- $gtext = '<?xml version="1.0" encoding="utf-8" ?>'
- .'<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">'
- .'<text>' . $gtext . '</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 .= "Content-Length: " . strlen($gtext) . "\r\n";
- $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
- $out .= "Connection: Close\r\n\r\n";
- $out .= $gtext;
- fwrite($fp, $out);
-
- while (!feof($fp))
- $store .= fgets($fp, 128);
- fclose($fp);
- }
-
- if (!$store) {
- $this->error = "Empty result from spelling engine";
- }
-
- preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER);
-
- // skip exceptions (if appropriate options are enabled)
- if (!empty($this->options['ignore_syms']) || !empty($this->options['ignore_nums'])
- || !empty($this->options['ignore_caps']) || !empty($this->options['dictionary'])
- ) {
- foreach ($matches as $idx => $m) {
- $word = mb_substr($text, $m[1], $m[2], RCMAIL_CHARSET);
- // skip exceptions
- if ($this->is_exception($word)) {
- unset($matches[$idx]);
- }
- }
- }
-
- return $matches;
- }
-
-
- private function _googie_words($text = null, $is_html=false)
- {
- if ($text) {
- if ($is_html) {
- $text = $this->html2text($text);
- }
-
- $matches = $this->_googie_check($text);
- }
- else {
- $matches = $this->matches;
- $text = $this->content;
- }
-
- $result = array();
-
- foreach ($matches as $m) {
- $result[] = mb_substr($text, $m[1], $m[2], RCMAIL_CHARSET);
- }
-
- return $result;
- }
-
-
- private function _googie_suggestions($word)
- {
- if ($word) {
- $matches = $this->_googie_check($word);
- }
- else {
- $matches = $this->matches;
- }
-
- if ($matches[0][4]) {
- $suggestions = explode("\t", $matches[0][4]);
- if (sizeof($suggestions) > self::MAX_SUGGESTIONS) {
- $suggestions = array_slice($suggestions, 0, MAX_SUGGESTIONS);
- }
-
- return $suggestions;
- }
-
- return array();
- }
-
-
- private function html2text($text)
- {
- $h2t = new html2text($text, false, true, 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')
- ." SET data = ?"
- ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
- ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
- implode(' ', $plugin['dictionary']), $plugin['language']);
- }
- // don't store empty dict
- else {
- $this->rc->db->query(
- "DELETE FROM " . $this->rc->db->table_name('dictionary')
- ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
- ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
- $plugin['language']);
- }
- }
- else if (!empty($this->dict)) {
- $this->rc->db->query(
- "INSERT INTO " .$this->rc->db->table_name('dictionary')
- ." (user_id, " . $this->rc->db->quoteIdentifier('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();
- $this->rc->db->query(
- "SELECT data FROM ".$this->rc->db->table_name('dictionary')
- ." WHERE user_id ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
- ." AND " . $this->rc->db->quoteIdentifier('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;
- }
-
-}
diff --git a/program/include/rcube_storage.php b/program/include/rcube_storage.php
deleted file mode 100644
index 933ebcc25..000000000
--- a/program/include/rcube_storage.php
+++ /dev/null
@@ -1,991 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_storage.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2012, The Roundcube Dev Team |
- | Copyright (C) 2012, Kolab Systems AG |
- | |
- | 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 Mail
- * @author Thomas Bruederli <roundcube@gmail.com>
- * @author Aleksander Machniak <alec@alec.pl>
- * @version 2.0
- */
-abstract class rcube_storage
-{
- /**
- * Instance of connection object e.g. rcube_imap_generic
- *
- * @var mixed
- */
- public $conn;
-
- protected $folder = 'INBOX';
- protected $default_charset = 'ISO-8859-1';
- protected $default_folders = array('INBOX');
- protected $search_set;
- protected $options = array('auth_method' => 'check');
- protected $page_size = 10;
- 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',
- '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);
- }
-
-
- /**
- * 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;
- }
-
-
- /**
- * This list of folders will be listed above all other folders
- *
- * @param array $arr Indexed list of folder names
- */
- public function set_default_folders($arr)
- {
- if (is_array($arr)) {
- $this->default_folders = $arr;
-
- // add inbox if not included
- if (!in_array('INBOX', $this->default_folders)) {
- array_unshift($this->default_folders, 'INBOX');
- }
- }
- }
-
-
- /**
- * 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)
- {
- $this->list_page = (int) $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]
- * @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 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 similar to the one generated by PEAR::Mail_mimeDecode
- *
- * @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 ? $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
- *
- * @return string Message source string
- */
- abstract function get_raw_body($uid, $fp = null);
-
-
- /**
- * Returns the message headers as string
- *
- * @param int $uid Message UID
- *
- * @return string Message headers string
- */
- abstract function get_raw_headers($uid);
-
-
- /**
- * 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 $message The message source string or filename
- * @param string $headers Headers string if $message contains only the body
- * @param boolean $is_file True if $message is a filename
- * @param array $flags Message flags
- * @param mixed $date Message internal date
- *
- * @return int|bool Appended message UID or True on success, False on error
- */
- 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);
- }
-
- 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
- *
- * @param string $folder Folder name
- *
- * @return int Folder status
- */
- abstract function folder_status($folder = null);
-
-
- /**
- * 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()
- {
- // create default folders if they do not exist
- foreach ($this->default_folders as $folder) {
- if (!$this->folder_exists($folder)) {
- $this->create_folder($folder, true);
- }
- else if (!$this->folder_exists($folder, true)) {
- $this->subscribe($folder);
- }
- }
- }
-
-
- /**
- * Get mailbox quota information.
- *
- * @return mixed Quota info or False if not supported
- */
- abstract function get_quota();
-
-
- /* -----------------------------------------
- * 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)
- *
- * @return array Metadata entry-value hash array on success, NULL on error
- */
- abstract function get_metadata($folder, $entries, $options = array());
-
-
- /* -----------------------------------------
- * 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 expunge_cache();
-
-} // end class rcube_storage
diff --git a/program/include/rcube_string_replacer.php b/program/include/rcube_string_replacer.php
deleted file mode 100644
index edb2ac34f..000000000
--- a/program/include/rcube_string_replacer.php
+++ /dev/null
@@ -1,194 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_string_replacer.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2009, 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 Core
- */
-class rcube_string_replacer
-{
- public static $pattern = '/##str_replacement\[([0-9]+)\]##/';
- public $mailto_pattern;
- public $link_pattern;
- private $values = array();
-
-
- function __construct()
- {
- // Simplified domain expression for UTF8 characters handling
- // Support unicode/punycode in top-level domain part
- $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
- $url1 = '.:;,';
- $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-';
-
- $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
- $this->mailto_pattern = "/("
- ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part
- ."@$utf_domain" // domain-part
- ."(\?[$url1$url2]+)?" // e.g. ?subject=test...
- .")/";
- }
-
- /**
- * Add a string to the internal list
- *
- * @param string String value
- * @return int Index of value for retrieval
- */
- public function add($str)
- {
- $i = count($this->values);
- $this->values[$i] = $str;
- return $i;
- }
-
- /**
- * Build replacement string
- */
- public function get_replacement($i)
- {
- return '##str_replacement['.$i.']##';
- }
-
- /**
- * Callback function used to build HTML links around URL strings
- *
- * @param array Matches result from preg_replace_callback
- * @return int Index of saved string value
- */
- public function link_callback($matches)
- {
- $i = -1;
- $scheme = strtolower($matches[1]);
-
- if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
- $url = $matches[1] . $matches[2];
- }
- else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) {
- $url = $m[2] . $matches[2];
- $url_prefix = 'http://';
- $prefix = $m[1];
- }
-
- if ($url) {
- $suffix = $this->parse_url_brackets($url);
- $i = $this->add($prefix . html::a(array(
- 'href' => $url_prefix . $url,
- 'target' => '_blank'
- ), rcmail::Q($url)) . $suffix);
- }
-
- // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes.
- return $i >= 0 ? $this->get_replacement($i) : $matches[0];
- }
-
- /**
- * Callback function used to build mailto: links around e-mail strings
- *
- * @param array Matches result from preg_replace_callback
- * @return int Index of saved string value
- */
- public function mailto_callback($matches)
- {
- $href = $matches[1];
- $suffix = $this->parse_url_brackets($href);
-
- $i = $this->add(html::a(array(
- 'href' => 'mailto:' . $href,
- 'onclick' => "return ".rcmail::JS_OBJECT_NAME.".command('compose','".rcmail::JQ($href)."',this)",
- ), rcmail::Q($href)) . $suffix);
-
- return $i >= 0 ? $this->get_replacement($i) : '';
- }
-
- /**
- * Look up the index from the preg_replace matches array
- * and return the substitution value.
- *
- * @param array Matches result from preg_replace_callback
- * @return string Value at index $matches[1]
- */
- public function replace_callback($matches)
- {
- return $this->values[$matches[1]];
- }
-
- /**
- * Replace all defined (link|mailto) patterns with replacement string
- *
- * @param string $str Text
- *
- * @return string Text
- */
- public function replace($str)
- {
- // search for patterns like links and e-mail addresses
- $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
- $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
-
- return $str;
- }
-
- /**
- * Replace substituted strings with original values
- */
- public function resolve($str)
- {
- return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
- }
-
- /**
- * Fixes bracket characters in URL handling
- */
- public static function parse_url_brackets(&$url)
- {
- // #1487672: special handling of square brackets,
- // URL regexp allows [] characters in URL, for example:
- // "http://example.com/?a[b]=c". However we need to handle
- // properly situation when a bracket is placed at the end
- // of the link e.g. "[http://example.com]"
- if (preg_match('/(\\[|\\])/', $url)) {
- $in = false;
- for ($i=0, $len=strlen($url); $i<$len; $i++) {
- if ($url[$i] == '[') {
- if ($in)
- break;
- $in = true;
- }
- else if ($url[$i] == ']') {
- if (!$in)
- break;
- $in = false;
- }
- }
-
- if ($i<$len) {
- $suffix = substr($url, $i);
- $url = substr($url, 0, $i);
- }
- }
-
- return $suffix;
- }
-
-}
diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php
deleted file mode 100644
index b92187ad4..000000000
--- a/program/include/rcube_user.php
+++ /dev/null
@@ -1,701 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_user.inc |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2010, 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> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Class representing a system user
- *
- * @package Core
- * @author Thomas Bruederli <roundcube@gmail.com>
- */
-class rcube_user
-{
- public $ID;
- public $data;
- public $language;
-
- /**
- * Holds database connection.
- *
- * @var rcube_db
- */
- private $db;
-
- /**
- * Framework object.
- *
- * @var rcube
- */
- private $rc;
-
- 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')." 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')
- * @return string Full user name or its part
- */
- function get_username($part = null)
- {
- if ($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 (!empty($this->language))
- $prefs = array('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']) {
- $prefs += (array)unserialize($this->data['preferences']);
- }
- }
-
- return $prefs;
- }
-
-
- /**
- * Write the given user prefs to the user's record
- *
- * @param array $a_user_prefs User prefs to save
- * @return boolean True on success, False on failure
- */
- function save_prefs($a_user_prefs)
- {
- if (!$this->ID)
- return false;
-
- $config = $this->rc->config;
- $old_prefs = (array)$this->get_prefs();
-
- // merge (partial) prefs array with existing settings
- $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 (!isset($old_prefs[$key]) && ($value == $config->get($key)))
- unset($save_prefs[$key]);
- }
-
- $save_prefs = serialize($save_prefs);
-
- $this->db->query(
- "UPDATE ".$this->db->table_name('users').
- " SET preferences = ?".
- ", language = ?".
- " WHERE user_id = ?",
- $save_prefs,
- $_SESSION['language'],
- $this->ID);
-
- $this->language = $_SESSION['language'];
-
- // Update success
- if ($this->db->affected_rows() !== false) {
- $config->set_user_prefs($a_user_prefs);
- $this->data['preferences'] = $save_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 ($this->db->is_replicated() && $config->get('session_storage', 'db') != 'db') {
- $_SESSION['preferences'] = $save_prefs;
- $_SESSION['preferences_time'] = time();
- $config->set_user_prefs($a_user_prefs);
- $this->data['preferences'] = $save_prefs;
- }
-
- return false;
- }
-
-
- /**
- * 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)
- {
- $result = $this->list_identities($id ? sprintf('AND identity_id = %d', $id) : '');
- return $result[0];
- }
-
-
- /**
- * Return a list of all identities linked with this user
- *
- * @param string $sql_add Optional WHERE clauses
- * @return array List of identities
- */
- function list_identities($sql_add = '')
- {
- $result = array();
-
- $sql_result = $this->db->query(
- "SELECT * FROM ".$this->db->table_name('identities').
- " WHERE del <> 1 AND user_id = ?".
- ($sql_add ? " ".$sql_add : "").
- " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC",
- $this->ID);
-
- while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $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->quoteIdentifier($col) . ' = ?';
- $query_params[] = $value;
- }
- $query_params[] = $iid;
- $query_params[] = $this->ID;
-
- $sql = "UPDATE ".$this->db->table_name('identities').
- " 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));
-
- 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->quoteIdentifier($col);
- $insert_values[] = $value;
- }
- $insert_cols[] = 'user_id';
- $insert_values[] = $this->ID;
-
- $sql = "INSERT INTO ".$this->db->table_name('identities').
- " (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));
-
- 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').
- " 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').
- " SET del = 1, changed = ".$this->db->now().
- " WHERE user_id = ?".
- " AND identity_id = ?",
- $this->ID,
- $iid);
-
- 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').
- " SET ".$this->db->quoteIdentifier('standard')." = '0'".
- " WHERE user_id = ?".
- " AND identity_id <> ?".
- " AND del <> 1",
- $this->ID,
- $iid);
- }
- }
-
-
- /**
- * Update user's last_login timestamp
- */
- function touch()
- {
- if ($this->ID) {
- $this->db->query(
- "UPDATE ".$this->db->table_name('users').
- " SET last_login = ".$this->db->now().
- " WHERE user_id = ?",
- $this->ID);
- }
- }
-
-
- /**
- * 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')
- ." 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') . " u"
- ." JOIN " . $dbh->table_name('identities') . " 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);
- else
- 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').
- " (created, last_login, username, mail_host, language)".
- " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)",
- strip_newlines($data['user']),
- strip_newlines($data['host']),
- strip_newlines($data['language']));
-
- if ($user_id = $dbh->insert_id('users')) {
- // create rcube_user instance to make plugin hooks work
- $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[] = strip_newlines($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['name'] = strip_newlines($record['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, ".$this->db->quoteIdentifier('name')
- ." FROM ".$this->db->table_name('searches')
- ." WHERE user_id = ?"
- ." AND ".$this->db->quoteIdentifier('type')." = ?"
- ." ORDER BY ".$this->db->quoteIdentifier('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 ".$this->db->quoteIdentifier('name')
- .", ".$this->db->quoteIdentifier('data')
- .", ".$this->db->quoteIdentifier('type')
- ." FROM ".$this->db->table_name('searches')
- ." 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')
- ." 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->quoteIdentifier('type');
- $insert_values[] = (int) $data['type'];
- $insert_cols[] = $this->db->quoteIdentifier('name');
- $insert_values[] = $data['name'];
- $insert_cols[] = $this->db->quoteIdentifier('data');
- $insert_values[] = serialize($data['data']);
-
- $sql = "INSERT INTO ".$this->db->table_name('searches')
- ." (".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');
- }
-
-}
diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php
deleted file mode 100644
index 2a4d4c482..000000000
--- a/program/include/rcube_utils.php
+++ /dev/null
@@ -1,885 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_utils.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2012, The Roundcube Dev Team |
- | Copyright (C) 2011-2012, Kolab Systems AG |
- | |
- | 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 common functions |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- | Author: Aleksander Machniak <alec@alec.pl> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Utility class providing common functions
- *
- * @package Core
- */
-class rcube_utils
-{
- // define constants for input reading
- const INPUT_GET = 0x0101;
- const INPUT_POST = 0x0102;
- const INPUT_GPC = 0x0103;
-
- /**
- * Helper method to set a cookie with the current path and host settings
- *
- * @param string Cookie name
- * @param string Cookie value
- * @param string Expiration time
- */
- public static function setcookie($name, $value, $exp = 0)
- {
- if (headers_sent()) {
- return;
- }
-
- $cookie = session_get_cookie_params();
- $secure = $cookie['secure'] || self::https_check();
-
- setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'], $secure, true);
- }
-
- /**
- * E-mail address validation.
- *
- * @param string $email Email address
- * @param boolean $dns_check True to check dns
- *
- * @return boolean True on success, False if address is invalid
- */
- public static function check_email($email, $dns_check=true)
- {
- // Check for invalid characters
- if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email)) {
- return false;
- }
-
- // Check for length limit specified by RFC 5321 (#1486453)
- if (strlen($email) > 254) {
- return false;
- }
-
- $email_array = explode('@', $email);
-
- // Check that there's one @ symbol
- if (count($email_array) < 2) {
- return false;
- }
-
- $domain_part = array_pop($email_array);
- $local_part = implode('@', $email_array);
-
- // from PEAR::Validate
- $regexp = '&^(?:
- ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name
- ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) #2 OR dot-atom (RFC5322)
- $&xi';
-
- if (!preg_match($regexp, $local_part)) {
- return false;
- }
-
- // Validate domain part
- if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) {
- return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address
- }
- else {
- // If not an IP address
- $domain_array = explode('.', $domain_part);
- // Not enough parts to be a valid domain
- if (sizeof($domain_array) < 2) {
- return false;
- }
-
- foreach ($domain_array as $part) {
- if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) {
- return false;
- }
- }
-
- // last domain part
- if (preg_match('/[^a-zA-Z]/', array_pop($domain_array))) {
- return false;
- }
-
- $rcube = rcube::get_instance();
-
- if (!$dns_check || !$rcube->config->get('email_dns_check')) {
- return true;
- }
-
- if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
- $lookup = array();
- @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
- foreach ($lookup as $line) {
- if (strpos($line, 'MX preference')) {
- return true;
- }
- }
- return false;
- }
-
- // find MX record(s)
- if (getmxrr($domain_part, $mx_records)) {
- return true;
- }
-
- // find any DNS record
- if (checkdnsrr($domain_part, 'ANY')) {
- return true;
- }
- }
-
- return false;
- }
-
-
- /**
- * Validates IPv4 or IPv6 address
- *
- * @param string $ip IP address in v4 or v6 format
- *
- * @return bool True if the address is valid
- */
- public static function check_ip($ip)
- {
- // IPv6, but there's no build-in IPv6 support
- if (strpos($ip, ':') !== false && !defined('AF_INET6')) {
- $parts = explode(':', $domain_part);
- $count = count($parts);
-
- if ($count > 8 || $count < 2) {
- return false;
- }
-
- foreach ($parts as $idx => $part) {
- $length = strlen($part);
- if (!$length) {
- // there can be only one ::
- if ($found_empty) {
- return false;
- }
- $found_empty = true;
- }
- // last part can be an IPv4 address
- else if ($idx == $count - 1) {
- if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
- return @inet_pton($part) !== false;
- }
- }
- else if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
- return false;
- }
- }
-
- return true;
- }
-
- return @inet_pton($ip) !== false;
- }
-
-
- /**
- * Check whether the HTTP referer matches the current request
- *
- * @return boolean True if referer is the same host+path, false if not
- */
- public static function check_referer()
- {
- $uri = parse_url($_SERVER['REQUEST_URI']);
- $referer = parse_url(self::request_header('Referer'));
- return $referer['host'] == self::request_header('Host') && $referer['path'] == $uri['path'];
- }
-
-
- /**
- * Replacing specials characters to a specific encoding type
- *
- * @param string Input string
- * @param string Encoding type: text|html|xml|js|url
- * @param string Replace mode for tags: show|replace|remove
- * @param boolean Convert newlines
- *
- * @return string The quoted string
- */
- public static function rep_specialchars_output($str, $enctype = '', $mode = '', $newlines = true)
- {
- static $html_encode_arr = false;
- static $js_rep_table = false;
- static $xml_rep_table = false;
-
- if (!is_string($str)) {
- $str = strval($str);
- }
-
- // encode for HTML output
- if ($enctype == 'html') {
- if (!$html_encode_arr) {
- $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
- unset($html_encode_arr['?']);
- }
-
- $encode_arr = $html_encode_arr;
-
- // don't replace quotes and html tags
- if ($mode == 'show' || $mode == '') {
- $ltpos = strpos($str, '<');
- if ($ltpos !== false && strpos($str, '>', $ltpos) !== false) {
- unset($encode_arr['"']);
- unset($encode_arr['<']);
- unset($encode_arr['>']);
- unset($encode_arr['&']);
- }
- }
- else if ($mode == 'remove') {
- $str = strip_tags($str);
- }
-
- $out = strtr($str, $encode_arr);
-
- return $newlines ? nl2br($out) : $out;
- }
-
- // if the replace tables for XML and JS are not yet defined
- if ($js_rep_table === false) {
- $js_rep_table = $xml_rep_table = array();
- $xml_rep_table['&'] = '&amp;';
-
- // can be increased to support more charsets
- for ($c=160; $c<256; $c++) {
- $xml_rep_table[chr($c)] = "&#$c;";
- }
-
- $xml_rep_table['"'] = '&quot;';
- $js_rep_table['"'] = '\\"';
- $js_rep_table["'"] = "\\'";
- $js_rep_table["\\"] = "\\\\";
- // Unicode line and paragraph separators (#1486310)
- $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
- $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
- }
-
- // encode for javascript use
- if ($enctype == 'js') {
- return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
- }
-
- // encode for plaintext
- if ($enctype == 'text') {
- return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
- }
-
- if ($enctype == 'url') {
- return rawurlencode($str);
- }
-
- // encode for XML
- if ($enctype == 'xml') {
- return strtr($str, $xml_rep_table);
- }
-
- // no encoding given -> return original string
- return $str;
- }
-
-
- /**
- * Read input value and convert it for internal use
- * Performs stripslashes() and charset conversion if necessary
- *
- * @param string Field name to read
- * @param int Source to get value from (GPC)
- * @param boolean Allow HTML tags in field value
- * @param string Charset to convert into
- *
- * @return string Field value or NULL if not available
- */
- public static function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
- {
- $value = NULL;
-
- if ($source == self::INPUT_GET) {
- if (isset($_GET[$fname])) {
- $value = $_GET[$fname];
- }
- }
- else if ($source == self::INPUT_POST) {
- if (isset($_POST[$fname])) {
- $value = $_POST[$fname];
- }
- }
- else if ($source == self::INPUT_GPC) {
- if (isset($_POST[$fname])) {
- $value = $_POST[$fname];
- }
- else if (isset($_GET[$fname])) {
- $value = $_GET[$fname];
- }
- else if (isset($_COOKIE[$fname])) {
- $value = $_COOKIE[$fname];
- }
- }
-
- return self::parse_input_value($value, $allow_html, $charset);
- }
-
-
- /**
- * Parse/validate input value. See self::get_input_value()
- * Performs stripslashes() and charset conversion if necessary
- *
- * @param string Input value
- * @param boolean Allow HTML tags in field value
- * @param string Charset to convert into
- *
- * @return string Parsed value
- */
- public static function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
- {
- global $OUTPUT;
-
- if (empty($value)) {
- return $value;
- }
-
- if (is_array($value)) {
- foreach ($value as $idx => $val) {
- $value[$idx] = self::parse_input_value($val, $allow_html, $charset);
- }
- return $value;
- }
-
- // strip single quotes if magic_quotes_sybase is enabled
- if (ini_get('magic_quotes_sybase')) {
- $value = str_replace("''", "'", $value);
- }
- // strip slashes if magic_quotes enabled
- else if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
- $value = stripslashes($value);
- }
-
- // remove HTML tags if not allowed
- if (!$allow_html) {
- $value = strip_tags($value);
- }
-
- $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
-
- // remove invalid characters (#1488124)
- if ($output_charset == 'UTF-8') {
- $value = rcube_charset::clean($value);
- }
-
- // convert to internal charset
- if ($charset && $output_charset) {
- $value = rcube_charset::convert($value, $output_charset, $charset);
- }
-
- return $value;
- }
-
-
- /**
- * Convert array of request parameters (prefixed with _)
- * to a regular array with non-prefixed keys.
- *
- * @param int $mode Source to get value from (GPC)
- * @param string $ignore PCRE expression to skip parameters by name
- *
- * @return array Hash array with all request parameters
- */
- public static function request2param($mode = null, $ignore = 'task|action')
- {
- $out = array();
- $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
-
- foreach ($src as $key => $value) {
- $fname = $key[0] == '_' ? substr($key, 1) : $key;
- if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) {
- $out[$fname] = self::get_input_value($key, $mode);
- }
- }
-
- return $out;
- }
-
-
- /**
- * Convert the given string into a valid HTML identifier
- * Same functionality as done in app.js with rcube_webmail.html_identifier()
- */
- public static function html_identifier($str, $encode=false)
- {
- if ($encode) {
- return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
- }
- else {
- return asciiwords($str, true, '_');
- }
- }
-
-
- /**
- * 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 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 ($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 = self::get_input_value($fname, self::INPUT_POST, true);
- $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
- }
-
- $out = $input->show($value);
-
- return $out;
- }
-
-
- /**
- * Replace all css definitions with #container [def]
- * and remove css-inlined scripting
- *
- * @param string CSS source code
- * @param string Container ID to use as prefix
- *
- * @return string Modified CSS source
- */
- public static function mod_css_styles($source, $container_id, $allow_remote=false)
- {
- $last_pos = 0;
- $replacements = new rcube_string_replacer;
-
- // ignore the whole block if evil styles are detected
- $source = self::xss_entity_decode($source);
- $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
- $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : '');
- if (preg_match("/$evilexpr/i", $stripped)) {
- return '/* evil! */';
- }
-
- // cut out all contents between { and }
- while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
- $styles = substr($source, $pos+1, $pos2-($pos+1));
-
- // check every line of a style block...
- if ($allow_remote) {
- $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
- foreach ($a_styles as $line) {
- $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
- // ... and only allow strict url() values
- $regexp = '!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims';
- if (stripos($stripped, 'url(') && !preg_match($regexp, $line)) {
- $a_styles = array('/* evil! */');
- break;
- }
- }
- $styles = join(";\n", $a_styles);
- }
-
- $key = $replacements->add($styles);
- $source = substr($source, 0, $pos+1)
- . $replacements->get_replacement($key)
- . substr($source, $pos2, strlen($source)-$pos2);
- $last_pos = $pos+2;
- }
-
- // remove html comments and add #container to each tag selector.
- // also replace body definition because we also stripped off the <body> tag
- $styles = preg_replace(
- array(
- '/(^\s*<!--)|(-->\s*$)/',
- '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
- '/'.preg_quote($container_id, '/').'\s+body/i',
- ),
- array(
- '',
- "\\1#$container_id \\2",
- $container_id,
- ),
- $source);
-
- // put block contents back in
- $styles = $replacements->resolve($styles);
-
- return $styles;
- }
-
-
- /**
- * Generate CSS classes from mimetype and filename extension
- *
- * @param string $mimetype Mimetype
- * @param string $filename Filename
- *
- * @return string CSS classes separated by space
- */
- public static function file2class($mimetype, $filename)
- {
- list($primary, $secondary) = explode('/', $mimetype);
-
- $classes = array($primary ? $primary : 'unknown');
- if ($secondary) {
- $classes[] = $secondary;
- }
- if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
- $classes[] = $m[1];
- }
-
- return strtolower(join(" ", $classes));
- }
-
-
- /**
- * Decode escaped entities used by known XSS exploits.
- * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
- *
- * @param string CSS content to decode
- *
- * @return string Decoded string
- */
- public static function xss_entity_decode($content)
- {
- $out = html_entity_decode(html_entity_decode($content));
- $out = preg_replace_callback('/\\\([0-9a-f]{4})/i',
- array(self, 'xss_entity_decode_callback'), $out);
- $out = preg_replace('#/\*.*\*/#Ums', '', $out);
-
- return $out;
- }
-
-
- /**
- * preg_replace_callback callback for xss_entity_decode
- *
- * @param array $matches Result from preg_replace_callback
- *
- * @return string Decoded entity
- */
- public static function xss_entity_decode_callback($matches)
- {
- return chr(hexdec($matches[1]));
- }
-
-
- /**
- * Check if we can process not exceeding memory_limit
- *
- * @param integer Required amount of memory
- *
- * @return boolean True if memory won't be exceeded, False otherwise
- */
- public static function mem_check($need)
- {
- $mem_limit = parse_bytes(ini_get('memory_limit'));
- $memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
-
- return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
- }
-
-
- /**
- * Check if working in SSL mode
- *
- * @param integer $port HTTPS port number
- * @param boolean $use_https Enables 'use_https' option checking
- *
- * @return boolean
- */
- public static function https_check($port=null, $use_https=true)
- {
- global $RCMAIL;
-
- if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') {
- return true;
- }
- if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') {
- return true;
- }
- if ($port && $_SERVER['SERVER_PORT'] == $port) {
- return true;
- }
- if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https')) {
- return true;
- }
-
- return false;
- }
-
-
- /**
- * Replaces hostname variables.
- *
- * @param string $name Hostname
- * @param string $host Optional IMAP hostname
- *
- * @return string Hostname
- */
- public static function parse_host($name, $host = '')
- {
- // %n - host
- $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
- // %t - host name without first part, e.g. %n=mail.domain.tld, %t=domain.tld
- $t = preg_replace('/^[^\.]+\./', '', $n);
- // %d - domain name without first part
- $d = preg_replace('/^[^\.]+\./', '', $_SERVER['HTTP_HOST']);
- // %h - IMAP host
- $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host;
- // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
- $z = preg_replace('/^[^\.]+\./', '', $h);
- // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
- if (strpos($name, '%s') !== false) {
- $user_email = self::get_input_value('_user', self::INPUT_POST);
- $user_email = self::idn_convert($user_email, true);
- $matches = preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s);
- if ($matches < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false) {
- return false;
- }
- }
-
- $name = str_replace(array('%n', '%t', '%d', '%h', '%z', '%s'), array($n, $t, $d, $h, $z, $s[2]), $name);
- return $name;
- }
-
-
- /**
- * Returns remote IP address and forwarded addresses if found
- *
- * @return string Remote IP address(es)
- */
- public static function remote_ip()
- {
- $address = $_SERVER['REMOTE_ADDR'];
-
- // append the NGINX X-Real-IP header, if set
- if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
- $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
- }
- // append the X-Forwarded-For header, if set
- if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
- $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
- }
-
- if (!empty($remote_ip)) {
- $address .= '(' . implode(',', $remote_ip) . ')';
- }
-
- return $address;
- }
-
-
- /**
- * Read a specific HTTP request header.
- *
- * @param string $name Header name
- *
- * @return mixed Header value or null if not available
- */
- public static function request_header($name)
- {
- if (function_exists('getallheaders')) {
- $hdrs = array_change_key_case(getallheaders(), CASE_UPPER);
- $key = strtoupper($name);
- }
- else {
- $key = 'HTTP_' . strtoupper(strtr($name, '-', '_'));
- $hdrs = array_change_key_case($_SERVER, CASE_UPPER);
- }
-
- return $hdrs[$key];
- }
-
- /**
- * Explode quoted string
- *
- * @param string Delimiter expression string for preg_match()
- * @param string Input string
- *
- * @return array String items
- */
- public static function explode_quoted_string($delimiter, $string)
- {
- $result = array();
- $strlen = strlen($string);
-
- for ($q=$p=$i=0; $i < $strlen; $i++) {
- if ($string[$i] == "\"" && $string[$i-1] != "\\") {
- $q = $q ? false : true;
- }
- else if (!$q && preg_match("/$delimiter/", $string[$i])) {
- $result[] = substr($string, $p, $i - $p);
- $p = $i + 1;
- }
- }
-
- $result[] = substr($string, $p);
-
- return $result;
- }
-
-
- /**
- * Improved equivalent to strtotime()
- *
- * @param string $date Date string
- *
- * @return int Unix timestamp
- */
- public static function strtotime($date)
- {
- // check for MS Outlook vCard date format YYYYMMDD
- if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
- return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
- }
- else if (is_numeric($date)) {
- return $date;
- }
-
- // support non-standard "GMTXXXX" literal
- $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
-
- // if date parsing fails, we have a date in non-rfc format.
- // remove token from the end and try again
- while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
- $d = explode(' ', $date);
- array_pop($d);
- if (!$d) {
- break;
- }
- $date = implode(' ', $d);
- }
-
- return $ts;
- }
-
-
- /*
- * Idn_to_ascii wrapper.
- * Intl/Idn modules version of this function doesn't work with e-mail address
- */
- public static function idn_to_ascii($str)
- {
- return self::idn_convert($str, true);
- }
-
-
- /*
- * Idn_to_ascii wrapper.
- * Intl/Idn modules version of this function doesn't work with e-mail address
- */
- public static function idn_to_utf8($str)
- {
- return self::idn_convert($str, false);
- }
-
-
- public static function idn_convert($input, $is_utf=false)
- {
- if ($at = strpos($input, '@')) {
- $user = substr($input, 0, $at);
- $domain = substr($input, $at+1);
- }
- else {
- $domain = $input;
- }
-
- $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
-
- if ($domain === false) {
- return '';
- }
-
- return $at ? $user . '@' . $domain : $domain;
- }
-
- /**
- * Split the given string into word tokens
- *
- * @param string Input to tokenize
- * @return array List of tokens
- */
- public static function tokenize_string($str)
- {
- return explode(" ", preg_replace(
- array('/[\s;\/+-]+/i', '/(\d)[-.\s]+(\d)/', '/\s\w{1,3}\s/u'),
- array(' ', '\\1\\2', ' '),
- $str));
- }
-
- /**
- * Normalize the given string for fulltext search.
- * Currently only optimized for Latin-1 characters; to be extended
- *
- * @param string Input string (UTF-8)
- * @param boolean True to return list of words as array
- * @return mixed Normalized string or a list of normalized tokens
- */
- public static function normalize_string($str, $as_array = false)
- {
- // split by words
- $arr = self::tokenize_string($str);
-
- foreach ($arr as $i => $part) {
- if (utf8_encode(utf8_decode($part)) == $part) { // is latin-1 ?
- $arr[$i] = utf8_encode(strtr(strtolower(strtr(utf8_decode($part),
- 'ÇçäâàåéêëèïîìÅÉöôòüûùÿøØáíóúñÑÁÂÀãÃÊËÈÍÎÏÓÔõÕÚÛÙýÝ',
- 'ccaaaaeeeeiiiaeooouuuyooaiounnaaaaaeeeiiioooouuuyy')),
- array('ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u')));
- }
- else
- $arr[$i] = mb_strtolower($part);
- }
-
- return $as_array ? $arr : join(" ", $arr);
- }
-
-}
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
deleted file mode 100644
index 00903c257..000000000
--- a/program/include/rcube_vcard.php
+++ /dev/null
@@ -1,825 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_vcard.php |
- | |
- | This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-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: |
- | Logical representation of a vcard address record |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Logical representation of a vcard-based address record
- * Provides functions to parse and export vCard data format
- *
- * @package Addressbook
- * @author Thomas Bruederli <roundcube@gmail.com>
- */
-class rcube_vcard
-{
- private static $values_decoded = false;
- private $raw = array(
- 'FN' => array(),
- 'N' => array(array('','','','','')),
- );
- private static $fieldmap = array(
- 'phone' => 'TEL',
- 'birthday' => 'BDAY',
- 'website' => 'URL',
- 'notes' => 'NOTE',
- 'email' => 'EMAIL',
- 'address' => 'ADR',
- 'jobtitle' => 'TITLE',
- 'department' => 'X-DEPARTMENT',
- 'gender' => 'X-GENDER',
- 'maidenname' => 'X-MAIDENNAME',
- 'anniversary' => 'X-ANNIVERSARY',
- 'assistant' => 'X-ASSISTANT',
- 'manager' => 'X-MANAGER',
- 'spouse' => 'X-SPOUSE',
- 'edit' => 'X-AB-EDIT',
- );
- private $typemap = array('IPHONE' => 'mobile', 'CELL' => 'mobile', 'WORK,FAX' => 'workfax');
- private $phonetypemap = array('HOME1' => 'HOME', 'BUSINESS1' => 'WORK', 'BUSINESS2' => 'WORK2', 'BUSINESSFAX' => 'WORK,FAX');
- 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 $notes;
- public $email = array();
-
- public static $eol = "\r\n";
-
- /**
- * Constructor
- */
- public function __construct($vcard = null, $charset = RCMAIL_CHARSET, $detect = false, $fieldmap = array())
- {
- if (!empty($fielmap))
- $this->extend_fieldmap($fieldmap);
-
- if (!empty($vcard))
- $this->load($vcard, $charset, $detect);
- }
-
-
- /**
- * Load record from (internal, unfolded) vcard 3.0 format
- *
- * @param string vCard string to parse
- * @param string Charset of string values
- * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required
- */
- public function load($vcard, $charset = RCMAIL_CHARSET, $detect = false)
- {
- self::$values_decoded = false;
- $this->raw = self::vcard_decode($vcard);
-
- // resolve charset parameters
- if ($charset == null) {
- $this->raw = self::charset_convert($this->raw);
- }
- // vcard has encoded values and charset should be detected
- else if ($detect && self::$values_decoded &&
- ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw))) && $detected_charset != RCMAIL_CHARSET) {
- $this->raw = self::charset_convert($this->raw, $detected_charset);
- }
-
- // consider FN empty if the same as the primary e-mail address
- if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0])
- $this->raw['FN'][0][0] = '';
-
- // find well-known address fields
- $this->displayname = $this->raw['FN'][0][0];
- $this->surname = $this->raw['N'][0][0];
- $this->firstname = $this->raw['N'][0][1];
- $this->middlename = $this->raw['N'][0][2];
- $this->nickname = $this->raw['NICKNAME'][0][0];
- $this->organization = $this->raw['ORG'][0][0];
- $this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
-
- foreach ((array)$this->raw['EMAIL'] as $i => $raw_email)
- $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
-
- // make the pref e-mail address the first entry in $this->email
- $pref_index = $this->get_type_index('EMAIL', 'pref');
- if ($pref_index > 0) {
- $tmp = $this->email[0];
- $this->email[0] = $this->email[$pref_index];
- $this->email[$pref_index] = $tmp;
- }
- }
-
-
- /**
- * Return vCard data as associative array to be unsed in Roundcube address books
- *
- * @return array Hash array with key-value pairs
- */
- public function get_assoc()
- {
- $out = array('name' => $this->displayname);
- $typemap = $this->typemap;
-
- // copy name fields to output array
- foreach (array('firstname','surname','middlename','nickname','organization') as $col) {
- if (strlen($this->$col))
- $out[$col] = $this->$col;
- }
-
- if ($this->raw['N'][0][3])
- $out['prefix'] = $this->raw['N'][0][3];
- if ($this->raw['N'][0][4])
- $out['suffix'] = $this->raw['N'][0][4];
-
- // convert from raw vcard data into associative data for Roundcube
- foreach (array_flip(self::$fieldmap) as $tag => $col) {
- foreach ((array)$this->raw[$tag] as $i => $raw) {
- if (is_array($raw)) {
- $k = -1;
- $key = $col;
- $subtype = '';
-
- if (!empty($raw['type'])) {
- $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true));
- $combined = strtoupper($combined);
-
- if ($typemap[$combined]) {
- $subtype = $typemap[$combined];
- }
- else if ($typemap[$raw['type'][++$k]]) {
- $subtype = $typemap[$raw['type'][$k]];
- }
- else {
- $subtype = strtolower($raw['type'][$k]);
- }
-
- while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref'))
- $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
- }
-
- // read vcard 2.1 subtype
- if (!$subtype) {
- foreach ($raw as $k => $v) {
- if (!is_numeric($k) && $v === true && ($k = strtolower($k))
- && !in_array($k, array('pref','internet','voice','base64'))
- ) {
- $k_uc = strtoupper($k);
- $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
- break;
- }
- }
- }
-
- // force subtype if none set
- if (!$subtype && preg_match('/^(email|phone|address|website)/', $key))
- $subtype = 'other';
-
- if ($subtype)
- $key .= ':' . $subtype;
-
- // split ADR values into assoc array
- if ($tag == 'ADR') {
- list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw;
- $out[$key][] = $value;
- }
- else
- $out[$key][] = $raw[0];
- }
- else {
- $out[$col][] = $raw;
- }
- }
- }
-
- // handle special IM fields as used by Apple
- foreach ($this->immap as $tag => $type) {
- foreach ((array)$this->raw[$tag] as $i => $raw) {
- $out['im:'.$type][] = $raw[0];
- }
- }
-
- // copy photo data
- if ($this->raw['PHOTO'])
- $out['photo'] = $this->raw['PHOTO'][0][0];
-
- return $out;
- }
-
-
- /**
- * 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);
- $typemap = array_flip($this->typemap);
-
- switch ($field) {
- case 'name':
- case 'displayname':
- $this->raw['FN'][0][0] = $value;
- break;
-
- case 'surname':
- $this->raw['N'][0][0] = $value;
- break;
-
- case 'firstname':
- $this->raw['N'][0][1] = $value;
- break;
-
- case 'middlename':
- $this->raw['N'][0][2] = $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] = $value;
- break;
-
- case 'organization':
- $this->raw['ORG'][0][0] = $value;
- break;
-
- case 'photo':
- if (strpos($value, 'http:') === 0) {
- // TODO: fetch file from URL and save it locally?
- $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true);
- }
- else {
- $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value));
- }
- break;
-
- case 'email':
- $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc)));
- $this->email[] = $value;
- break;
-
- case 'im':
- // save IM subtypes into extension fields
- $typemap = array_flip($this->immap);
- if ($field = $typemap[strtolower($type)])
- $this->raw[$field][] = array(0 => $value);
- break;
-
- case 'birthday':
- case 'anniversary':
- if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field]))
- $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
- break;
-
- case 'address':
- if ($this->addresstypemap[$type_uc])
- $type = $this->addresstypemap[$type_uc];
-
- $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
-
- // fall through if not empty
- if (!strlen(join('', $value)))
- break;
-
- default:
- if ($field == 'phone' && $this->phonetypemap[$type_uc])
- $type = $this->phonetypemap[$type_uc];
-
- if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) {
- $index = count($this->raw[$tag]);
- $this->raw[$tag][$index] = (array)$value;
- if ($type)
- $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type));
- }
- break;
- }
- }
-
- /**
- * Setter for individual vcard properties
- *
- * @param string VCard tag name
- * @param array Value-set of this vcard property
- * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set
- */
- public function set_raw($tag, $value, $append = false)
- {
- $index = $append ? count($this->raw[$tag]) : 0;
- $this->raw[$tag][$index] = (array)$value;
- }
-
-
- /**
- * Find index with the '$type' attribute
- *
- * @param string Field name
- * @return int Field index having $type set
- */
- private function get_type_index($field, $type = 'pref')
- {
- $result = 0;
- if ($this->raw[$field]) {
- foreach ($this->raw[$field] as $i => $data) {
- if (is_array($data['type']) && in_array_nocase('pref', $data['type']))
- $result = $i;
- }
- }
-
- 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 != RCMAIL_CHARSET) {
- $data = rcube_charset::convert($data, $charset);
- $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
- $charset = RCMAIL_CHARSET;
- }
-
- $vcard_block = '';
- $in_vcard_block = false;
-
- foreach (preg_split("/[\r\n]+/", $data) as $i => $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(self::cleanup($vcard_block), $charset, true, self::$fieldmap);
- if (!empty($obj->displayname) || !empty($obj->email))
- $out[] = $obj;
-
- $in_vcard_block = false;
- }
- else if (preg_match('/^BEGIN:VCARD$/i', $line)) {
- $vcard_block = $line . "\n";
- $in_vcard_block = true;
- }
- }
-
- return $out;
- }
-
-
- /**
- * Normalize vcard data for better parsing
- *
- * @param string vCard block
- * @return string Cleaned vcard block
- */
- private static function cleanup($vcard)
- {
- // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
- $vcard = preg_replace(
- '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
- '\2;type=\5\3:\4',
- $vcard);
-
- // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility
- $vcard = preg_replace_callback(
- '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
- array('self', 'x_abrelatednames_callback'),
- $vcard);
-
- // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines
- $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard);
-
- // convert X-WAB-GENDER to X-GENDER
- if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
- $value = $matches[1] == '2' ? 'male' : 'female';
- $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard);
- }
-
- // if N doesn't have any semicolons, add some
- $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard);
-
- return $vcard;
- }
-
- 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);
- $data = array();
-
- for ($i=0; $i < count($lines); $i++) {
- if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line))
- continue;
-
- if (preg_match('/^(BEGIN|END)$/i', $line[1]))
- continue;
-
- // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
- if (($data['VERSION'][0] == "2.1") && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2) && !preg_match('/^TYPE=/i', $regs2[2])) {
- $line[1] = $regs2[1];
- foreach (explode(';', $regs2[2]) as $prop)
- $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
- }
-
- if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
- $entry = array();
- $field = strtoupper($regs2[1][0]);
- $enc = null;
-
- foreach($regs2[1] as $attrid => $attr) {
- if ((list($key, $value) = explode('=', $attr)) && $value) {
- $value = trim($value);
- if ($key == 'ENCODING') {
- $value = strtoupper($value);
- // add next line(s) to value string if QP line end detected
- if ($value == 'QUOTED-PRINTABLE') {
- while (preg_match('/=$/', $lines[$i]))
- $line[2] .= "\n" . $lines[++$i];
- }
- $enc = $value;
- }
- else {
- $lc_key = strtolower($key);
- $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ','));
- }
- }
- else if ($attrid > 0) {
- $entry[strtolower($key)] = true; // true means attr without =value
- }
- }
-
- // decode value
- if ($enc || !empty($entry['base64'])) {
- // save encoding type (#1488432)
- if ($enc == 'B') {
- $entry['encoding'] = 'B';
- // should we use vCard 3.0 instead?
- // $entry['base64'] = true;
- }
- $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64');
- }
-
- if ($enc != 'B' && empty($entry['base64'])) {
- $line[2] = self::vcard_unquote($line[2]);
- }
-
- $entry = array_merge($entry, (array) $line[2]);
- $data[$field][] = $entry;
- }
- }
-
- unset($data['VERSION']);
- return $data;
- }
-
-
- /**
- * Decode a given string with the encoding rule from ENCODING attributes
- *
- * @param string String to decode
- * @param string Encoding type (quoted-printable and base64 supported)
- * @return string Decoded 8bit value
- */
- private static function decode_value($value, $encoding)
- {
- switch (strtolower($encoding)) {
- case 'quoted-printable':
- self::$values_decoded = true;
- return quoted_printable_decode($value);
-
- case 'base64':
- case 'b':
- self::$values_decoded = true;
- return base64_decode($value);
-
- default:
- return $value;
- }
- }
-
-
- /**
- * 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)) {
- if ($attrvalues) {
- $attr .= strtoupper(";$attrname"); // true means just tag, not tag=value, as in PHOTO;BASE64:...
- }
- }
- 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
- */
- private static function vcard_quote($s, $sep = ';')
- {
- if (is_array($s)) {
- foreach($s as $part) {
- $r[] = self::vcard_quote($part, $sep);
- }
- return(implode($sep, (array)$r));
- }
- else {
- return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
- }
- }
-
-
- /**
- * Split quoted string
- *
- * @param string vCard string to split
- * @param string Separator char/string
- * @return array List with splited values
- */
- private static function vcard_unquote($s, $sep = ';')
- {
- // break string into parts separated by $sep, but leave escaped $sep alone
- if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) {
- foreach($parts as $s) {
- $result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep);
- }
- return $result;
- }
- else {
- return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':'));
- }
- }
-
-
- /**
- * Check if vCard entry is empty: empty string or an array with
- * all entries empty.
- *
- * @param mixed $value Attribute value (string or array)
- *
- * @return bool True if the value is empty, False otherwise
- */
- private static function is_empty($value)
- {
- foreach ((array)$value as $v) {
- if (((string)$v) !== '') {
- return false;
- }
- }
-
- 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)
- {
- 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';
-
- // use mb_detect_encoding()
- $encodings = array('UTF-8', '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', 'BIG5', 'GB2312');
-
- if (function_exists('mb_detect_encoding') && ($enc = mb_detect_encoding($string, $encodings)))
- return $enc;
-
- // 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 rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); # fallback to Latin-1
- }
-
-}