Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2513431
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
129 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/ext/Roundcube/rcmail.php b/lib/ext/Roundcube/rcmail.php
deleted file mode 100644
index a6b0bcd..0000000
--- a/lib/ext/Roundcube/rcmail.php
+++ /dev/null
@@ -1,2122 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcmail.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: |
- | Application class providing core functions and holding |
- | instances of all 'global' objects like db- and imap-connections |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- | Author: Aleksander Machniak <alec@alec.pl> |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Application class of Roundcube Webmail
- * implemented as singleton
- *
- * @package Core
- */
-class rcmail extends rcube
-{
- /**
- * Main tasks.
- *
- * @var array
- */
- static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
-
- /**
- * Current task.
- *
- * @var string
- */
- public $task;
-
- /**
- * Current action.
- *
- * @var string
- */
- public $action = '';
- public $comm_path = './';
-
- private $address_books = array();
- private $action_map = array();
-
-
- const JS_OBJECT_NAME = 'rcmail';
-
- const ERROR_STORAGE = -2;
- const ERROR_INVALID_REQUEST = 1;
- const ERROR_INVALID_HOST = 2;
- const ERROR_COOKIES_DISABLED = 3;
-
-
- /**
- * This implements the 'singleton' design pattern
- *
- * @return rcmail The one and only instance
- */
- static function get_instance()
- {
- if (!self::$instance || !is_a(self::$instance, 'rcmail')) {
- self::$instance = new rcmail();
- self::$instance->startup(); // init AFTER object was linked with self::$instance
- }
-
- return self::$instance;
- }
-
-
- /**
- * Initial startup function
- * to register session, create database and imap connections
- */
- protected function startup()
- {
- $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS);
-
- // start session
- $this->session_init();
-
- // 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));
-
- // reset some session parameters when changing task
- if ($this->task != 'utils') {
- if ($this->session && $_SESSION['task'] != $this->task)
- $this->session->remove('page');
- // set current task to session
- $_SESSION['task'] = $this->task;
- }
-
- // init output class
- if (!empty($_REQUEST['_remote']))
- $GLOBALS['OUTPUT'] = $this->json_init();
- else
- $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
-
- // load plugins
- $this->plugins->init($this, $this->task);
- $this->plugins->load_plugins((array)$this->config->get('plugins', array()), array('filesystem_attachments', 'jqueryui'));
- }
-
-
- /**
- * Setter for application task
- *
- * @param string Task to set
- */
- public function set_task($task)
- {
- $task = asciiwords($task);
-
- if ($this->user && $this->user->ID)
- $task = !$task ? 'mail' : $task;
- else
- $task = 'login';
-
- $this->task = $task;
- $this->comm_path = $this->url(array('task' => $this->task));
-
- if ($this->output)
- $this->output->set_env('task', $this->task);
- }
-
-
- /**
- * Setter for system user object
- *
- * @param rcube_user Current user instance
- */
- public function set_user($user)
- {
- if (is_object($user)) {
- $this->user = $user;
-
- // overwrite config with user preferences
- $this->config->set_user_prefs((array)$this->user->get_prefs());
- }
-
- $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
-
- // set localization
- setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
-
- // workaround for http://bugs.php.net/bug.php?id=18556
- if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
- setlocale(LC_CTYPE, 'en_US' . '.utf8');
- }
-
-
- /**
- * Return instance of the internal address book class
- *
- * @param string Address book identifier (-1 for default addressbook)
- * @param boolean True if the address book needs to be writeable
- *
- * @return rcube_contacts Address book object
- */
- public function get_address_book($id, $writeable = false)
- {
- $contacts = null;
- $ldap_config = (array)$this->config->get('ldap_public');
-
- // 'sql' is the alias for '0' used by autocomplete
- if ($id == 'sql')
- $id = '0';
- else if ($id == -1) {
- $id = $this->config->get('default_addressbook');
- $default = true;
- }
-
- // use existing instance
- if (isset($this->address_books[$id]) && ($this->address_books[$id] instanceof rcube_addressbook)) {
- $contacts = $this->address_books[$id];
- }
- else if ($id && $ldap_config[$id]) {
- $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['storage_host']));
- }
- else if ($id === '0') {
- $contacts = new rcube_contacts($this->db, $this->get_user_id());
- }
- else {
- $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
-
- // plugin returned instance of a rcube_addressbook
- if ($plugin['instance'] instanceof rcube_addressbook) {
- $contacts = $plugin['instance'];
- }
- }
-
- // 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));
- if (!empty($source)) {
- $contacts = $this->get_address_book($source['id']);
- if ($contacts)
- $id = $source['id'];
- }
- }
-
- if (!$contacts) {
- self::raise_error(array(
- 'code' => 700, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Addressbook source ($id) not found!"),
- true, true);
- }
-
- if ($writeable && $contacts->readonly) {
- return null;
- }
-
- // set configured sort order
- 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;
- }
-
-
- /**
- * Return address books list
- *
- * @param boolean True if the address book needs to be writeable
- *
- * @return array Address books array
- */
- public function get_address_sources($writeable = false)
- {
- $abook_type = strtolower($this->config->get('address_book_type'));
- $ldap_config = $this->config->get('ldap_public');
- $autocomplete = (array) $this->config->get('autocomplete_addressbooks');
- $list = array();
-
- // We are using the DB address book
- if ($abook_type != 'ldap') {
- if (!isset($this->address_books['0']))
- $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id());
- $list['0'] = array(
- 'id' => '0',
- 'name' => $this->gettext('personaladrbook'),
- 'groups' => $this->address_books['0']->groups,
- 'readonly' => $this->address_books['0']->readonly,
- 'autocomplete' => in_array('sql', $autocomplete),
- 'undelete' => $this->address_books['0']->undelete && $this->config->get('undo_timeout'),
- );
- }
-
- if ($ldap_config) {
- $ldap_config = (array) $ldap_config;
- foreach ($ldap_config as $id => $prop) {
- // handle misconfiguration
- if (empty($prop) || !is_array($prop)) {
- continue;
- }
- $list[$id] = array(
- 'id' => $id,
- 'name' => $prop['name'],
- 'groups' => is_array($prop['groups']),
- 'readonly' => !$prop['writable'],
- 'hidden' => $prop['hidden'],
- 'autocomplete' => in_array($id, $autocomplete)
- );
- }
- }
-
- $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
- $list = $plugin['sources'];
-
- foreach ($list as $idx => $item) {
- // register source for shutdown function
- 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'])
- unset($list[$idx]);
- }
-
- return $list;
- }
-
-
- /**
- * Init output object for GUI and add common scripts.
- * This will instantiate a rcmail_template 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
- */
- 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);
-
- // set keep-alive/check-recent interval
- if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
- $this->output->set_env('keep_alive', $keep_alive);
- }
-
- if ($framed) {
- $this->comm_path .= '&_framed=1';
- $this->output->set_env('framed', true);
- }
-
- $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);
-
- // add some basic labels to client
- $this->output->add_label('loading', 'servererror', 'requesttimedout');
-
- return $this->output;
- }
-
-
- /**
- * Create an output object for JSON responses
- *
- * @return rcube_output_json Reference to JSON output object
- */
- public function json_init()
- {
- if (!($this->output instanceof rcube_output_json))
- $this->output = new rcube_output_json($this->task);
-
- return $this->output;
- }
-
-
- /**
- * Create session object and start the session.
- */
- public function session_init()
- {
- parent::session_init();
-
- // set initial session vars
- if (!$_SESSION['user_id'])
- $_SESSION['temp'] = true;
-
- // restore skin selection after logout
- if ($_SESSION['temp'] && !empty($_SESSION['skin']))
- $this->config->set('skin', $_SESSION['skin']);
- }
-
-
- /**
- * Perfom login to the mail server and to the webmail service.
- * This will also create a new user entry if auto_create_user is configured.
- *
- * @param string Mail storage (IMAP) user name
- * @param string Mail storage (IMAP) password
- * @param string Mail storage (IMAP) host
- * @param bool Enables cookie check
- *
- * @return boolean True on success, False on failure
- */
- function login($username, $pass, $host = null, $cookiecheck = false)
- {
- $this->login_error = null;
-
- if (empty($username)) {
- return false;
- }
-
- if ($cookiecheck && empty($_COOKIE)) {
- $this->login_error = self::ERROR_COOKIES_DISABLED;
- return false;
- }
-
- $config = $this->config->all();
-
- if (!$host)
- $host = $config['default_host'];
-
- // Validate that selected host is in the list of configured hosts
- if (is_array($config['default_host'])) {
- $allowed = false;
- foreach ($config['default_host'] as $key => $host_allowed) {
- if (!is_numeric($key))
- $host_allowed = $key;
- if ($host == $host_allowed) {
- $allowed = true;
- break;
- }
- }
- if (!$allowed) {
- $host = null;
- }
- }
- else if (!empty($config['default_host']) && $host != rcube_utils::parse_host($config['default_host'])) {
- $host = null;
- }
-
- if (!$host) {
- $this->login_error = self::ERROR_INVALID_HOST;
- return false;
- }
-
- // parse $host URL
- $a_host = parse_url($host);
- if ($a_host['host']) {
- $host = $a_host['host'];
- $ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
- if (!empty($a_host['port']))
- $port = $a_host['port'];
- else if ($ssl && $ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
- $port = 993;
- }
-
- if (!$port) {
- $port = $config['default_port'];
- }
-
- /* Modify username with domain if required
- Inspired by Marco <P0L0_notspam_binware.org>
- */
- // Check if we need to add domain
- if (!empty($config['username_domain']) && strpos($username, '@') === false) {
- if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
- $username .= '@'.rcube_utils::parse_host($config['username_domain'][$host], $host);
- else if (is_string($config['username_domain']))
- $username .= '@'.rcube_utils::parse_host($config['username_domain'], $host);
- }
-
- // Convert username to lowercase. If storage backend
- // is case-insensitive we need to store always the same username (#1487113)
- if ($config['login_lc']) {
- $username = mb_strtolower($username);
- }
-
- // try to resolve email address from virtuser table
- if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
- $username = $virtuser;
- }
-
- // Here we need IDNA ASCII
- // Only rcube_contacts class is using domain names in Unicode
- $host = rcube_utils::idn_to_ascii($host);
- if (strpos($username, '@')) {
- // lowercase domain name
- list($local, $domain) = explode('@', $username);
- $username = $local . '@' . mb_strtolower($domain);
- $username = rcube_utils::idn_to_ascii($username);
- }
-
- // user already registered -> overwrite username
- if ($user = rcube_user::query($username, $host))
- $username = $user->data['username'];
-
- $storage = $this->get_storage();
-
- // try to log in
- if (!($login = $storage->connect($host, $username, $pass, $port, $ssl))) {
- // try with lowercase
- $username_lc = mb_strtolower($username);
- if ($username_lc != $username) {
- // try to find user record again -> overwrite username
- if (!$user && ($user = rcube_user::query($username_lc, $host)))
- $username_lc = $user->data['username'];
-
- if ($login = $storage->connect($host, $username_lc, $pass, $port, $ssl))
- $username = $username_lc;
- }
- }
-
- // exit if login failed
- if (!$login) {
- return false;
- }
-
- // user already registered -> update user's record
- if (is_object($user)) {
- // update last login timestamp
- $user->touch();
- }
- // create new system user
- else if ($config['auto_create_user']) {
- if ($created = rcube_user::create($username, $host)) {
- $user = $created;
- }
- else {
- self::raise_error(array(
- 'code' => 620, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Failed to create a user record. Maybe aborted by a plugin?"
- ), true, false);
- }
- }
- else {
- self::raise_error(array(
- 'code' => 621, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
- ), true, false);
- }
-
- // login succeeded
- if (is_object($user) && $user->ID) {
- // 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);
-
- // create default folders on first login
- if ($config['create_default_folders'] && (!empty($created) || empty($user->data['last_login']))) {
- $storage->create_default_folders();
- }
-
- // set session vars
- $_SESSION['user_id'] = $user->ID;
- $_SESSION['username'] = $user->data['username'];
- $_SESSION['storage_host'] = $host;
- $_SESSION['storage_port'] = $port;
- $_SESSION['storage_ssl'] = $ssl;
- $_SESSION['password'] = $this->encrypt($pass);
- $_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']);
-
- // force reloading complete list of subscribed mailboxes
- $storage->clear_cache('mailboxes', true);
-
- return true;
- }
-
- return false;
- }
-
-
- /**
- * Returns error code of last login operation
- *
- * @return int Error code
- */
- public function login_error()
- {
- if ($this->login_error) {
- return $this->login_error;
- }
-
- if ($this->storage && $this->storage->get_error_code() < -1) {
- return self::ERROR_STORAGE;
- }
- }
-
-
- /**
- * Auto-select IMAP host based on the posted login information
- *
- * @return string Selected IMAP host
- */
- public function autoselect_host()
- {
- $default_host = $this->config->get('default_host');
- $host = null;
-
- if (is_array($default_host)) {
- $post_host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
- $post_user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST);
-
- list($user, $domain) = explode('@', $post_user);
-
- // direct match in default_host array
- if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
- $host = $post_host;
- }
- // try to select host by mail domain
- else if (!empty($domain)) {
- foreach ($default_host as $storage_host => $mail_domains) {
- if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
- $host = $storage_host;
- break;
- }
- else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) {
- $host = is_numeric($storage_host) ? $mail_domains : $storage_host;
- break;
- }
- }
- }
-
- // take the first entry if $host is still not set
- if (empty($host)) {
- list($key, $val) = each($default_host);
- $host = is_numeric($key) ? $val : $key;
- }
- }
- else if (empty($default_host)) {
- $host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
- }
- else
- $host = rcube_utils::parse_host($default_host);
-
- return $host;
- }
-
-
- /**
- * Destroy session data and remove cookie
- */
- public function kill_session()
- {
- $this->plugins->exec_hook('session_destroy');
-
- $this->session->kill();
- $_SESSION = array('language' => $this->user->language, 'temp' => true, 'skin' => $this->config->get('skin'));
- $this->user->reset();
- }
-
-
- /**
- * Do server side actions on logout
- */
- public function logout_actions()
- {
- $config = $this->config->all();
- $storage = $this->get_storage();
-
- if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
- $storage->clear_folder($config['trash_mbox']);
- }
-
- if ($config['logout_expunge']) {
- $storage->expunge_folder('INBOX');
- }
-
- // Try to save unsaved user preferences
- if (!empty($_SESSION['preferences'])) {
- $this->user->save_prefs(unserialize($_SESSION['preferences']));
- }
- }
-
-
- /**
- * Generate a unique token to be used in a form request
- *
- * @return string The request token
- */
- public function get_request_token()
- {
- $sess_id = $_COOKIE[ini_get('session.name')];
- if (!$sess_id) $sess_id = session_id();
-
- $plugin = $this->plugins->exec_hook('request_token', array(
- 'value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id)));
-
- return $plugin['value'];
- }
-
-
- /**
- * Check if the current request contains a valid token
- *
- * @param int Request method
- * @return boolean True if request token is valid false if not
- */
- public function check_request($mode = rcube_utils::INPUT_POST)
- {
- $token = rcube_utils::get_input_value('_token', $mode);
- $sess_id = $_COOKIE[ini_get('session.name')];
- return !empty($sess_id) && $token == $this->get_request_token();
- }
-
-
- /**
- * Create unique authorization hash
- *
- * @param string Session ID
- * @param int Timestamp
- * @return string The generated auth hash
- */
- private function get_auth_hash($sess_id, $ts)
- {
- $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
- $sess_id,
- $ts,
- $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
- $_SERVER['HTTP_USER_AGENT']);
-
- if (function_exists('sha1'))
- return sha1($auth_string);
- else
- return md5($auth_string);
- }
-
-
- /**
- * 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)
- {
- if (!is_array($p)) {
- if (strpos($p, 'http') === 0)
- return $p;
-
- $p = array('_action' => @func_get_arg(0));
- }
-
- $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
- $p['_task'] = $task;
- unset($p['task']);
-
- $url = './';
- $delm = '?';
- foreach (array_reverse($p) as $key => $val) {
- if ($val !== '' && $val !== null) {
- $par = $key[0] == '_' ? $key : '_'.$key;
- $url .= $delm.urlencode($par).'='.urlencode($val);
- $delm = '&';
- }
- }
- return $url;
- }
-
-
- /**
- * Function to be executed in script shutdown
- */
- public function shutdown()
- {
- parent::shutdown();
-
- foreach ($this->address_books as $book) {
- if (is_object($book) && is_a($book, 'rcube_addressbook'))
- $book->close();
- }
-
- // before closing the database connection, write session data
- if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
- session_write_close();
- }
-
- // write performance stats to logs/console
- if ($this->config->get('devel_mode')) {
- if (function_exists('memory_get_usage'))
- $mem = $this->show_bytes(memory_get_usage());
- if (function_exists('memory_get_peak_usage'))
- $mem .= '/'.$this->show_bytes(memory_get_peak_usage());
-
- $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
- if (defined('RCMAIL_START'))
- self::print_timer(RCMAIL_START, $log);
- else
- self::console($log);
- }
- }
-
- /**
- * Registers action aliases for current task
- *
- * @param array $map Alias-to-filename hash array
- */
- public function register_action_map($map)
- {
- if (is_array($map)) {
- foreach ($map as $idx => $val) {
- $this->action_map[$idx] = $val;
- }
- }
- }
-
- /**
- * Returns current action filename
- *
- * @param array $map Alias-to-filename hash array
- */
- public function get_action_file()
- {
- if (!empty($this->action_map[$this->action])) {
- return $this->action_map[$this->action];
- }
-
- 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.
- * Now we need to add the prefix on servers where personal namespace has prefix.
- *
- * @param rcube_user $user User object
- */
- private function fix_namespace_settings($user)
- {
- $prefix = $this->storage->get_namespace('prefix');
- $prefix_len = strlen($prefix);
-
- if (!$prefix_len)
- return;
-
- $prefs = $this->config->all();
- if (!empty($prefs['namespace_fixed']))
- return;
-
- // Build namespace prefix regexp
- $ns = $this->storage->get_namespace();
- $regexp = array();
-
- foreach ($ns as $entry) {
- if (!empty($entry)) {
- foreach ($entry as $item) {
- if (strlen($item[0])) {
- $regexp[] = preg_quote($item[0], '/');
- }
- }
- }
- }
- $regexp = '/^('. implode('|', $regexp).')/';
-
- // Fix preferences
- $opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox');
- foreach ($opts as $opt) {
- if ($value = $prefs[$opt]) {
- if ($value != 'INBOX' && !preg_match($regexp, $value)) {
- $prefs[$opt] = $prefix.$value;
- }
- }
- }
-
- if (!empty($prefs['default_folders'])) {
- foreach ($prefs['default_folders'] as $idx => $name) {
- if ($name != 'INBOX' && !preg_match($regexp, $name)) {
- $prefs['default_folders'][$idx] = $prefix.$name;
- }
- }
- }
-
- if (!empty($prefs['search_mods'])) {
- $folders = array();
- foreach ($prefs['search_mods'] as $idx => $value) {
- if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) {
- $idx = $prefix.$idx;
- }
- $folders[$idx] = $value;
- }
- $prefs['search_mods'] = $folders;
- }
-
- if (!empty($prefs['message_threading'])) {
- $folders = array();
- foreach ($prefs['message_threading'] as $idx => $value) {
- if ($idx != 'INBOX' && !preg_match($regexp, $idx)) {
- $idx = $prefix.$idx;
- }
- $folders[$prefix.$idx] = $value;
- }
- $prefs['message_threading'] = $folders;
- }
-
- if (!empty($prefs['collapsed_folders'])) {
- $folders = explode('&&', $prefs['collapsed_folders']);
- $count = count($folders);
- $folders_str = '';
-
- if ($count) {
- $folders[0] = substr($folders[0], 1);
- $folders[$count-1] = substr($folders[$count-1], 0, -1);
- }
-
- foreach ($folders as $value) {
- if ($value != 'INBOX' && !preg_match($regexp, $value)) {
- $value = $prefix.$value;
- }
- $folders_str .= '&'.$value.'&';
- }
- $prefs['collapsed_folders'] = $folders_str;
- }
-
- $prefs['namespace_fixed'] = true;
-
- // save updated preferences and reset imap settings (default folders)
- $user->save_prefs($prefs);
- $this->set_storage_prop();
- }
-
-
- /**
- * Overwrite action variable
- *
- * @param string New action value
- */
- public function overwrite_action($action)
- {
- $this->action = $action;
- $this->output->set_env('action', $action);
- }
-
-
- /**
- * Send the given message using the configured method.
- *
- * @param object $message Reference to Mail_MIME object
- * @param string $from Sender address string
- * @param array $mailto Array of recipient address strings
- * @param array $smtp_error SMTP error array (reference)
- * @param string $body_file Location of file with saved message body (reference),
- * used when delay_file_io is enabled
- * @param array $smtp_opts SMTP options (e.g. DSN request)
- *
- * @return boolean Send status.
- */
- public function deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file = null, $smtp_opts = null)
- {
- $headers = $message->headers();
-
- // send thru SMTP server using custom SMTP library
- if ($this->config->get('smtp_server')) {
- // generate list of recipients
- $a_recipients = array($mailto);
-
- if (strlen($headers['Cc']))
- $a_recipients[] = $headers['Cc'];
- if (strlen($headers['Bcc']))
- $a_recipients[] = $headers['Bcc'];
-
- // clean Bcc from header for recipients
- $send_headers = $headers;
- unset($send_headers['Bcc']);
- // here too, it because txtHeaders() below use $message->_headers not only $send_headers
- unset($message->_headers['Bcc']);
-
- $smtp_headers = $message->txtHeaders($send_headers, true);
-
- if ($message->getParam('delay_file_io')) {
- // use common temp dir
- $temp_dir = $this->config->get('temp_dir');
- $body_file = tempnam($temp_dir, 'rcmMsg');
- if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
- self::raise_error(array('code' => 650, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not create message: ".$mime_result->getMessage()),
- TRUE, FALSE);
- return false;
- }
- $msg_body = fopen($body_file, 'r');
- }
- else {
- $msg_body = $message->get();
- }
-
- // send message
- if (!is_object($this->smtp)) {
- $this->smtp_init(true);
- }
-
- $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
- $smtp_response = $this->smtp->get_response();
- $smtp_error = $this->smtp->get_error();
-
- // log error
- if (!$sent) {
- self::raise_error(array('code' => 800, 'type' => 'smtp',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
- }
- }
- // send mail using PHP's mail() function
- else {
- // unset some headers because they will be added by the mail() function
- $headers_enc = $message->headers($headers);
- $headers_php = $message->_headers;
- unset($headers_php['To'], $headers_php['Subject']);
-
- // reset stored headers and overwrite
- $message->_headers = array();
- $header_str = $message->txtHeaders($headers_php);
-
- // #1485779
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
- if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
- $headers_enc['To'] = implode(', ', $m[1]);
- }
- }
-
- $msg_body = $message->get();
-
- if (PEAR::isError($msg_body)) {
- self::raise_error(array('code' => 650, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not create message: ".$msg_body->getMessage()),
- TRUE, FALSE);
- }
- else {
- $delim = $this->config->header_delimiter();
- $to = $headers_enc['To'];
- $subject = $headers_enc['Subject'];
- $header_str = rtrim($header_str);
-
- if ($delim != "\r\n") {
- $header_str = str_replace("\r\n", $delim, $header_str);
- $msg_body = str_replace("\r\n", $delim, $msg_body);
- $to = str_replace("\r\n", $delim, $to);
- $subject = str_replace("\r\n", $delim, $subject);
- }
-
- if (ini_get('safe_mode'))
- $sent = mail($to, $subject, $msg_body, $header_str);
- else
- $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
- }
- }
-
- if ($sent) {
- $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
-
- // remove MDN headers after sending
- unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
-
- // get all recipients
- if ($headers['Cc'])
- $mailto .= $headers['Cc'];
- if ($headers['Bcc'])
- $mailto .= $headers['Bcc'];
- if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
- $mailto = implode(', ', array_unique($m[1]));
-
- if ($this->config->get('smtp_log')) {
- self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
- $this->user->get_username(),
- $_SERVER['REMOTE_ADDR'],
- $mailto,
- !empty($smtp_response) ? join('; ', $smtp_response) : ''));
- }
- }
-
- if (is_resource($msg_body)) {
- fclose($msg_body);
- }
-
- $message->_headers = array();
- $message->headers($headers);
-
- return $sent;
- }
-
-
- /**
- * Unique Message-ID generator.
- *
- * @return string Message-ID
- */
- public function gen_message_id()
- {
- $local_part = md5(uniqid('rcmail'.mt_rand(),true));
- $domain_part = $this->user->get_username('domain');
-
- // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
- if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
- foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
- $host = preg_replace('/:[0-9]+$/', '', $host);
- if ($host && preg_match('/\.[a-z]+$/i', $host)) {
- $domain_part = $host;
- }
- }
- }
-
- return sprintf('<%s@%s>', $local_part, $domain_part);
- }
-
-
- /**
- * Returns RFC2822 formatted current date in user's timezone
- *
- * @return string Date
- */
- public function user_date()
- {
- // get user's timezone
- try {
- $tz = new DateTimeZone($this->config->get('timezone'));
- $date = new DateTime('now', $tz);
- }
- catch (Exception $e) {
- $date = new DateTime();
- }
-
- return $date->format('r');
- }
-
-
- /**
- * Write login data (name, ID, IP address) to the 'userlogins' log file.
- */
- public function log_login()
- {
- if (!$this->config->get('log_logins')) {
- return;
- }
-
- $user_name = $this->get_user_name();
- $user_id = $this->get_user_id();
-
- if (!$user_id) {
- return;
- }
-
- self::write_log('userlogins',
- sprintf('Successful login for %s (ID: %d) from %s in session %s',
- $user_name, $user_id, rcube_utils::remote_ip(), session_id()));
- }
-
-
- /**
- * Create a HTML table based on the given data
- *
- * @param array Named table attributes
- * @param mixed Table row data. Either a two-dimensional array or a valid SQL result set
- * @param array List of cols to show
- * @param string Name of the identifier col
- *
- * @return string HTML table code
- */
- public function table_output($attrib, $table_data, $a_show_cols, $id_col)
- {
- $table = new html_table(/*array('cols' => count($a_show_cols))*/);
-
- // add table header
- if (!$attrib['noheader']) {
- foreach ($a_show_cols as $col) {
- $table->add_header($col, $this->Q($this->gettext($col)));
- }
- }
-
- if (!is_array($table_data)) {
- $db = $this->get_dbh();
- while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) {
- $table->add_row(array('id' => 'rcmrow' . rcube_utils::html_identifier($sql_arr[$id_col])));
-
- // format each col
- foreach ($a_show_cols as $col) {
- $table->add($col, $this->Q($sql_arr[$col]));
- }
- }
- }
- else {
- foreach ($table_data as $row_data) {
- $class = !empty($row_data['class']) ? $row_data['class'] : '';
- $rowid = 'rcmrow' . rcube_utils::html_identifier($row_data[$id_col]);
-
- $table->add_row(array('id' => $rowid, 'class' => $class));
-
- // format each col
- foreach ($a_show_cols as $col) {
- $table->add($col, $this->Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
- }
- }
- }
-
- return $table->show($attrib);
- }
-
-
- /**
- * Convert the given date to a human readable form
- * This uses the date formatting properties from config
- *
- * @param mixed Date representation (string, timestamp or DateTime object)
- * @param string Date format to use
- * @param bool Enables date convertion according to user timezone
- *
- * @return string Formatted date string
- */
- public function format_date($date, $format = null, $convert = true)
- {
- if (is_object($date) && is_a($date, 'DateTime')) {
- $timestamp = $date->format('U');
- }
- else {
- if (!empty($date)) {
- $timestamp = rcube_strtotime($date);
- }
-
- if (empty($timestamp)) {
- return '';
- }
-
- try {
- $date = new DateTime("@".$timestamp);
- }
- catch (Exception $e) {
- return '';
- }
- }
-
- if ($convert) {
- try {
- // convert to the right timezone
- $stz = date_default_timezone_get();
- $tz = new DateTimeZone($this->config->get('timezone'));
- $date->setTimezone($tz);
- date_default_timezone_set($tz->getName());
-
- $timestamp = $date->format('U');
- }
- catch (Exception $e) {
- }
- }
-
- // define date format depending on current time
- if (!$format) {
- $now = time();
- $now_date = getdate($now);
- $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
- $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
- $pretty_date = $this->config->get('prettydate');
-
- if ($pretty_date && $timestamp > $today_limit && $timestamp < $now) {
- $format = $this->config->get('date_today', $this->config->get('time_format', 'H:i'));
- $today = true;
- }
- else if ($pretty_date && $timestamp > $week_limit && $timestamp < $now) {
- $format = $this->config->get('date_short', 'D H:i');
- }
- else {
- $format = $this->config->get('date_long', 'Y-m-d H:i');
- }
- }
-
- // strftime() format
- if (preg_match('/%[a-z]+/i', $format)) {
- $format = strftime($format, $timestamp);
- if ($stz) {
- date_default_timezone_set($stz);
- }
- return $today ? ($this->gettext('today') . ' ' . $format) : $format;
- }
-
- // parse format string manually in order to provide localized weekday and month names
- // an alternative would be to convert the date() format string to fit with strftime()
- $out = '';
- for ($i=0; $i<strlen($format); $i++) {
- if ($format[$i] == "\\") { // skip escape chars
- continue;
- }
-
- // write char "as-is"
- if ($format[$i] == ' ' || $format[$i-1] == "\\") {
- $out .= $format[$i];
- }
- // weekday (short)
- else if ($format[$i] == 'D') {
- $out .= $this->gettext(strtolower(date('D', $timestamp)));
- }
- // weekday long
- else if ($format[$i] == 'l') {
- $out .= $this->gettext(strtolower(date('l', $timestamp)));
- }
- // month name (short)
- else if ($format[$i] == 'M') {
- $out .= $this->gettext(strtolower(date('M', $timestamp)));
- }
- // month name (long)
- else if ($format[$i] == 'F') {
- $out .= $this->gettext('long'.strtolower(date('M', $timestamp)));
- }
- else if ($format[$i] == 'x') {
- $out .= strftime('%x %X', $timestamp);
- }
- else {
- $out .= date($format[$i], $timestamp);
- }
- }
-
- if ($today) {
- $label = $this->gettext('today');
- // replcae $ character with "Today" label (#1486120)
- if (strpos($out, '$') !== false) {
- $out = preg_replace('/\$/', $label, $out, 1);
- }
- else {
- $out = $label . ' ' . $out;
- }
- }
-
- if ($stz) {
- date_default_timezone_set($stz);
- }
-
- return $out;
- }
-
-
- /**
- * Return folders list in HTML
- *
- * @param array $attrib Named parameters
- *
- * @return string HTML code for the gui object
- */
- public function folder_list($attrib)
- {
- static $a_mailboxes;
-
- $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
-
- $rcmail = rcmail::get_instance();
- $storage = $rcmail->get_storage();
-
- // add some labels to client
- $rcmail->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
-
- $type = $attrib['type'] ? $attrib['type'] : 'ul';
- unset($attrib['type']);
-
- if ($type == 'ul' && !$attrib['id']) {
- $attrib['id'] = 'rcmboxlist';
- }
-
- if (empty($attrib['folder_name'])) {
- $attrib['folder_name'] = '*';
- }
-
- // get current folder
- $mbox_name = $storage->get_folder();
-
- // build the folders tree
- if (empty($a_mailboxes)) {
- // get mailbox list
- $a_folders = $storage->list_folders_subscribed(
- '', $attrib['folder_name'], $attrib['folder_filter']);
- $delimiter = $storage->get_hierarchy_delimiter();
- $a_mailboxes = array();
-
- foreach ($a_folders as $folder) {
- $rcmail->build_folder_tree($a_mailboxes, $folder, $delimiter);
- }
- }
-
- // allow plugins to alter the folder tree or to localize folder names
- $hook = $rcmail->plugins->exec_hook('render_mailboxlist', array(
- 'list' => $a_mailboxes,
- 'delimiter' => $delimiter,
- 'type' => $type,
- 'attribs' => $attrib,
- ));
-
- $a_mailboxes = $hook['list'];
- $attrib = $hook['attribs'];
-
- if ($type == 'select') {
- $attrib['is_escaped'] = true;
- $select = new html_select($attrib);
-
- // add no-selection option
- if ($attrib['noselection']) {
- $select->add(html::quote($rcmail->gettext($attrib['noselection'])), '');
- }
-
- $rcmail->render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
- $out = $select->show($attrib['default']);
- }
- else {
- $js_mailboxlist = array();
- $out = html::tag('ul', $attrib, $rcmail->render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
-
- $rcmail->output->add_gui_object('mailboxlist', $attrib['id']);
- $rcmail->output->set_env('mailboxes', $js_mailboxlist);
- $rcmail->output->set_env('unreadwrap', $attrib['unreadwrap']);
- $rcmail->output->set_env('collapsed_folders', (string)$rcmail->config->get('collapsed_folders'));
- }
-
- return $out;
- }
-
-
- /**
- * Return folders list as html_select object
- *
- * @param array $p Named parameters
- *
- * @return html_select HTML drop-down object
- */
- public function folder_selector($p = array())
- {
- $p += array('maxlength' => 100, 'realnames' => false, 'is_escaped' => true);
- $a_mailboxes = array();
- $storage = $this->get_storage();
-
- if (empty($p['folder_name'])) {
- $p['folder_name'] = '*';
- }
-
- if ($p['unsubscribed']) {
- $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
- }
- else {
- $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
- }
-
- $delimiter = $storage->get_hierarchy_delimiter();
-
- foreach ($list as $folder) {
- if (empty($p['exceptions']) || !in_array($folder, $p['exceptions'])) {
- $this->build_folder_tree($a_mailboxes, $folder, $delimiter);
- }
- }
-
- $select = new html_select($p);
-
- if ($p['noselection']) {
- $select->add(html::quote($p['noselection']), '');
- }
-
- $this->render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
-
- return $select;
- }
-
-
- /**
- * Create a hierarchical array of the mailbox list
- */
- public function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
- {
- // Handle namespace prefix
- $prefix = '';
- if (!$path) {
- $n_folder = $folder;
- $folder = $this->storage->mod_folder($folder);
-
- if ($n_folder != $folder) {
- $prefix = substr($n_folder, 0, -strlen($folder));
- }
- }
-
- $pos = strpos($folder, $delm);
-
- if ($pos !== false) {
- $subFolders = substr($folder, $pos+1);
- $currentFolder = substr($folder, 0, $pos);
-
- // sometimes folder has a delimiter as the last character
- if (!strlen($subFolders)) {
- $virtual = false;
- }
- else if (!isset($arrFolders[$currentFolder])) {
- $virtual = true;
- }
- else {
- $virtual = $arrFolders[$currentFolder]['virtual'];
- }
- }
- else {
- $subFolders = false;
- $currentFolder = $folder;
- $virtual = false;
- }
-
- $path .= $prefix . $currentFolder;
-
- if (!isset($arrFolders[$currentFolder])) {
- $arrFolders[$currentFolder] = array(
- 'id' => $path,
- 'name' => rcube_charset::convert($currentFolder, 'UTF7-IMAP'),
- 'virtual' => $virtual,
- 'folders' => array());
- }
- else {
- $arrFolders[$currentFolder]['virtual'] = $virtual;
- }
-
- if (strlen($subFolders)) {
- $this->build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
- }
- }
-
-
- /**
- * Return html for a structured list <ul> for the mailbox tree
- */
- public function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
- {
- $maxlength = intval($attrib['maxlength']);
- $realnames = (bool)$attrib['realnames'];
- $msgcounts = $this->storage->get_cache('messagecount');
- $collapsed = $this->config->get('collapsed_folders');
-
- $out = '';
- foreach ($arrFolders as $key => $folder) {
- $title = null;
- $folder_class = $this->folder_classname($folder['id']);
- $is_collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
- $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
-
- if ($folder_class && !$realnames) {
- $foldername = $this->gettext($folder_class);
- }
- else {
- $foldername = $folder['name'];
-
- // shorten the folder name to a given length
- if ($maxlength && $maxlength > 1) {
- $fname = abbreviate_string($foldername, $maxlength);
- if ($fname != $foldername) {
- $title = $foldername;
- }
- $foldername = $fname;
- }
- }
-
- // make folder name safe for ids and class names
- $folder_id = rcube_utils::html_identifier($folder['id'], true);
- $classes = array('mailbox');
-
- // set special class for Sent, Drafts, Trash and Junk
- if ($folder_class) {
- $classes[] = $folder_class;
- }
-
- if ($folder['id'] == $mbox_name) {
- $classes[] = 'selected';
- }
-
- if ($folder['virtual']) {
- $classes[] = 'virtual';
- }
- else if ($unread) {
- $classes[] = 'unread';
- }
-
- $js_name = $this->JQ($folder['id']);
- $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),
- 'rel' => $folder['id'],
- 'title' => $title,
- );
-
- $out .= html::tag('li', array(
- 'id' => "rcmli".$folder_id,
- 'class' => join(' ', $classes),
- 'noclose' => true),
- html::a($link_attrib, $html_name) .
- (!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)
- ), ' ') : ''));
-
- $jslist[$folder_id] = array(
- 'id' => $folder['id'],
- 'name' => $foldername,
- 'virtual' => $folder['virtual']
- );
-
- if (!empty($folder['folders'])) {
- $out .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)),
- $this->render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
- }
-
- $out .= "</li>\n";
- }
-
- return $out;
- }
-
-
- /**
- * Return html for a flat list <select> for the mailbox tree
- */
- public function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = array())
- {
- $out = '';
-
- foreach ($arrFolders as $key => $folder) {
- // skip exceptions (and its subfolders)
- if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
- continue;
- }
-
- // skip folders in which it isn't possible to create subfolders
- if (!empty($opts['skip_noinferiors'])) {
- $attrs = $this->storage->folder_attributes($folder['id']);
- if ($attrs && in_array('\\Noinferiors', $attrs)) {
- continue;
- }
- }
-
- if (!$realnames && ($folder_class = $this->folder_classname($folder['id']))) {
- $foldername = $this->gettext($folder_class);
- }
- else {
- $foldername = $folder['name'];
-
- // shorten the folder name to a given length
- if ($maxlength && $maxlength > 1) {
- $foldername = abbreviate_string($foldername, $maxlength);
- }
- }
-
- $select->add(str_repeat(' ', $nestLevel*4) . html::quote($foldername), $folder['id']);
-
- if (!empty($folder['folders'])) {
- $out .= $this->render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
- $select, $realnames, $nestLevel+1, $opts);
- }
- }
-
- return $out;
- }
-
-
- /**
- * Return internal name for the given folder if it matches the configured special folders
- */
- public function folder_classname($folder_id)
- {
- if ($folder_id == 'INBOX') {
- return 'inbox';
- }
-
- // for these mailboxes we have localized labels and css classes
- foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
- {
- if ($folder_id === $this->config->get($smbx.'_mbox')) {
- return $smbx;
- }
- }
- }
-
-
- /**
- * Try to localize the given IMAP folder name.
- * UTF-7 decode it in case no localized text was found
- *
- * @param string $name Folder name
- *
- * @return string Localized folder name in UTF-8 encoding
- */
- public function localize_foldername($name)
- {
- if ($folder_class = $this->folder_classname($name)) {
- return $this->gettext($folder_class);
- }
- else {
- return rcube_charset::convert($name, 'UTF7-IMAP');
- }
- }
-
-
- public function localize_folderpath($path)
- {
- $protect_folders = $this->config->get('protect_default_folders');
- $default_folders = (array) $this->config->get('default_folders');
- $delimiter = $this->storage->get_hierarchy_delimiter();
- $path = explode($delimiter, $path);
- $result = array();
-
- foreach ($path as $idx => $dir) {
- $directory = implode($delimiter, array_slice($path, 0, $idx+1));
- if ($protect_folders && in_array($directory, $default_folders)) {
- unset($result);
- $result[] = $this->localize_foldername($directory);
- }
- else {
- $result[] = rcube_charset::convert($dir, 'UTF7-IMAP');
- }
- }
-
- return implode($delimiter, $result);
- }
-
-
- public static function quota_display($attrib)
- {
- $rcmail = rcmail::get_instance();
-
- if (!$attrib['id']) {
- $attrib['id'] = 'rcmquotadisplay';
- }
-
- $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
-
- $rcmail->output->add_gui_object('quotadisplay', $attrib['id']);
-
- $quota = $rcmail->quota_content($attrib);
-
- $rcmail->output->add_script('rcmail.set_quota('.rcube_output::json_serialize($quota).');', 'docready');
-
- return html::span($attrib, '');
- }
-
-
- public function quota_content($attrib = null)
- {
- $quota = $this->storage->get_quota();
- $quota = $this->plugins->exec_hook('quota', $quota);
-
- $quota_result = (array) $quota;
- $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
-
- if (!$quota['total'] && $this->config->get('quota_zero_as_unlimited')) {
- $quota_result['title'] = $this->gettext('unlimited');
- $quota_result['percent'] = 0;
- }
- else if ($quota['total']) {
- if (!isset($quota['percent'])) {
- $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
- }
-
- $title = sprintf('%s / %s (%.0f%%)',
- $this->show_bytes($quota['used'] * 1024), $this->show_bytes($quota['total'] * 1024),
- $quota_result['percent']);
-
- $quota_result['title'] = $title;
-
- if ($attrib['width']) {
- $quota_result['width'] = $attrib['width'];
- }
- if ($attrib['height']) {
- $quota_result['height'] = $attrib['height'];
- }
- }
- else {
- $quota_result['title'] = $this->gettext('unknown');
- $quota_result['percent'] = 0;
- }
-
- return $quota_result;
- }
-
-
- /**
- * Outputs error message according to server error/response codes
- *
- * @param string $fallback Fallback message label
- * @param array $fallback_args Fallback message label arguments
- */
- public function display_server_error($fallback = null, $fallback_args = null)
- {
- $err_code = $this->storage->get_error_code();
- $res_code = $this->storage->get_response_code();
-
- if ($err_code < 0) {
- $this->output->show_message('storageerror', 'error');
- }
- else if ($res_code == rcube_storage::NOPERM) {
- $this->output->show_message('errornoperm', 'error');
- }
- else if ($res_code == rcube_storage::READONLY) {
- $this->output->show_message('errorreadonly', 'error');
- }
- else if ($err_code && ($err_str = $this->storage->get_error_str())) {
- // try to detect access rights problem and display appropriate message
- if (stripos($err_str, 'Permission denied') !== false) {
- $this->output->show_message('errornoperm', 'error');
- }
- else {
- $this->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
- }
- }
- else if ($fallback) {
- $this->output->show_message($fallback, 'error', $fallback_args);
- }
- }
-
-
- /**
- * Output HTML editor scripts
- *
- * @param string $mode Editor mode
- */
- public function html_editor($mode = '')
- {
- $hook = $this->plugins->exec_hook('html_editor', array('mode' => $mode));
-
- if ($hook['abort']) {
- return;
- }
-
- $lang = strtolower($_SESSION['language']);
-
- // TinyMCE uses two-letter lang codes, with exception of Chinese
- if (strpos($lang, 'zh_') === 0) {
- $lang = str_replace('_', '-', $lang);
- }
- else {
- $lang = substr($lang, 0, 2);
- }
-
- if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js')) {
- $lang = 'en';
- }
-
- $script = json_encode(array(
- 'mode' => $mode,
- 'lang' => $lang,
- 'skin_path' => $this->output->get_skin_path(),
- 'spellcheck' => intval($this->config->get('enable_spellcheck')),
- 'spelldict' => intval($this->config->get('spellcheck_dictionary'))
- ));
-
- $this->output->include_script('tiny_mce/tiny_mce.js');
- $this->output->include_script('editor.js');
- $this->output->add_script("rcmail_editor_init($script)", 'docready');
- }
-
-
- /**
- * Replaces TinyMCE's emoticon images with plain-text representation
- *
- * @param string $html HTML content
- *
- * @return string HTML content
- */
- public static function replace_emoticons($html)
- {
- $emoticons = array(
- '8-)' => 'smiley-cool',
- ':-#' => 'smiley-foot-in-mouth',
- ':-*' => 'smiley-kiss',
- ':-X' => 'smiley-sealed',
- ':-P' => 'smiley-tongue-out',
- ':-@' => 'smiley-yell',
- ":'(" => 'smiley-cry',
- ':-(' => 'smiley-frown',
- ':-D' => 'smiley-laughing',
- ':-)' => 'smiley-smile',
- ':-S' => 'smiley-undecided',
- ':-$' => 'smiley-embarassed',
- 'O:-)' => 'smiley-innocent',
- ':-|' => 'smiley-money-mouth',
- ':-O' => 'smiley-surprised',
- ';-)' => 'smiley-wink',
- );
-
- foreach ($emoticons as $idx => $file) {
- // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
- $search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
- $replace[] = $idx;
- }
-
- return preg_replace($search, $replace, $html);
- }
-
-
- /**
- * File upload progress handler.
- */
- public function upload_progress()
- {
- $prefix = ini_get('apc.rfc1867_prefix');
- $params = array(
- 'action' => $this->action,
- 'name' => rcube_utils::get_input_value('_progress', rcube_utils::INPUT_GET),
- );
-
- if (function_exists('apc_fetch')) {
- $status = apc_fetch($prefix . $params['name']);
-
- if (!empty($status)) {
- $status['percent'] = round($status['current']/$status['total']*100);
- $params = array_merge($status, $params);
- }
- }
-
- if (isset($params['percent']))
- $params['text'] = $this->gettext(array('name' => 'uploadprogress', 'vars' => array(
- 'percent' => $params['percent'] . '%',
- 'current' => $this->show_bytes($params['current']),
- 'total' => $this->show_bytes($params['total'])
- )));
-
- $this->output->command('upload_progress_update', $params);
- $this->output->send();
- }
-
-
- /**
- * Initializes file uploading interface.
- */
- public function upload_init()
- {
- // Enable upload progress bar
- if (($seconds = $this->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
- if ($field_name = ini_get('apc.rfc1867_name')) {
- $this->output->set_env('upload_progress_name', $field_name);
- $this->output->set_env('upload_progress_time', (int) $seconds);
- }
- }
-
- // find max filesize value
- $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
- $max_postsize = parse_bytes(ini_get('post_max_size'));
- if ($max_postsize && $max_postsize < $max_filesize) {
- $max_filesize = $max_postsize;
- }
-
- $this->output->set_env('max_filesize', $max_filesize);
- $max_filesize = self::show_bytes($max_filesize);
- $this->output->set_env('filesizeerror', $this->gettext(array(
- 'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
-
- return $max_filesize;
- }
-
-
- /**
- * Initializes client-side autocompletion.
- */
- public function autocomplete_init()
- {
- static $init;
-
- if ($init) {
- return;
- }
-
- $init = 1;
-
- if (($threads = (int)$this->config->get('autocomplete_threads')) > 0) {
- $book_types = (array) $this->config->get('autocomplete_addressbooks', 'sql');
- if (count($book_types) > 1) {
- $this->output->set_env('autocomplete_threads', $threads);
- $this->output->set_env('autocomplete_sources', $book_types);
- }
- }
-
- $this->output->set_env('autocomplete_max', (int)$this->config->get('autocomplete_max', 15));
- $this->output->set_env('autocomplete_min_length', $this->config->get('autocomplete_min_length'));
- $this->output->add_label('autocompletechars', 'autocompletemore');
- }
-
-
- /**
- * Returns supported font-family specifications
- *
- * @param string $font Font name
- *
- * @param string|array Font-family specification array or string (if $font is used)
- */
- public static function font_defs($font = null)
- {
- $fonts = array(
- 'Andale Mono' => '"Andale Mono",Times,monospace',
- 'Arial' => 'Arial,Helvetica,sans-serif',
- 'Arial Black' => '"Arial Black","Avant Garde",sans-serif',
- 'Book Antiqua' => '"Book Antiqua",Palatino,serif',
- 'Courier New' => '"Courier New",Courier,monospace',
- 'Georgia' => 'Georgia,Palatino,serif',
- 'Helvetica' => 'Helvetica,Arial,sans-serif',
- 'Impact' => 'Impact,Chicago,sans-serif',
- 'Tahoma' => 'Tahoma,Arial,Helvetica,sans-serif',
- 'Terminal' => 'Terminal,Monaco,monospace',
- 'Times New Roman' => '"Times New Roman",Times,serif',
- 'Trebuchet MS' => '"Trebuchet MS",Geneva,sans-serif',
- 'Verdana' => 'Verdana,Geneva,sans-serif',
- );
-
- if ($font) {
- return $fonts[$font];
- }
-
- return $fonts;
- }
-
-
- /**
- * Create a human readable string for a number of bytes
- *
- * @param int Number of bytes
- *
- * @return string Byte string
- */
- public function show_bytes($bytes)
- {
- if ($bytes >= 1073741824) {
- $gb = $bytes/1073741824;
- $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . $this->gettext('GB');
- }
- else if ($bytes >= 1048576) {
- $mb = $bytes/1048576;
- $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . $this->gettext('MB');
- }
- else if ($bytes >= 1024) {
- $str = sprintf("%d ", round($bytes/1024)) . $this->gettext('KB');
- }
- else {
- $str = sprintf('%d ', $bytes) . $this->gettext('B');
- }
-
- return $str;
- }
-
-
- /**
- * 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
- *
- * @return string Part size (and unit)
- */
- public function message_part_size($part)
- {
- if (isset($part->d_parameters['size'])) {
- $size = $this->show_bytes((int)$part->d_parameters['size']);
- }
- else {
- $size = $part->size;
- if ($part->encoding == 'base64') {
- $size = $size / 1.33;
- }
-
- $size = '~' . $this->show_bytes($size);
- }
-
- return $size;
- }
-
-
- /************************************************************************
- ********* Deprecated methods (to be removed) *********
- ***********************************************************************/
-
- public static function setcookie($name, $value, $exp = 0)
- {
- rcube_utils::setcookie($name, $value, $exp);
- }
-
- public function imap_connect()
- {
- return $this->storage_connect();
- }
-
- public function imap_init()
- {
- return $this->storage_init();
- }
-
- /**
- * Connect to the mail storage server with stored session data
- *
- * @return bool True on success, False on error
- */
- public function storage_connect()
- {
- $storage = $this->get_storage();
-
- if ($_SESSION['storage_host'] && !$storage->is_connected()) {
- $host = $_SESSION['storage_host'];
- $user = $_SESSION['username'];
- $port = $_SESSION['storage_port'];
- $ssl = $_SESSION['storage_ssl'];
- $pass = $this->decrypt($_SESSION['password']);
-
- if (!$storage->connect($host, $user, $pass, $port, $ssl)) {
- if (is_object($this->output)) {
- $error = $storage->get_error_code() == -1 ? 'storageerror' : 'sessionerror';
- $this->output->show_message($error, 'error');
- }
- }
- else {
- $this->set_storage_prop();
- return $storage->is_connected();
- }
- }
-
- return false;
- }
-}
diff --git a/lib/ext/Syncroton/Command/SendMail.php b/lib/ext/Syncroton/Command/SendMail.php
index 0f19f7a..768eda4 100644
--- a/lib/ext/Syncroton/Command/SendMail.php
+++ b/lib/ext/Syncroton/Command/SendMail.php
@@ -1,69 +1,69 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sendmail command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_SendMail extends Syncroton_Command_Wbxml
{
protected $_defaultNameSpace = 'uri:ComposeMail';
protected $_documentElement = 'Sendmail';
protected $_saveInSent;
protected $_itemId;
protected $_collectionId;
protected $_mime;
/**
* process the XML file and add, change, delete or fetches data
*
* @return resource
*/
public function handle()
{
if ($this->_requestParameters['contentType'] == 'message/rfc822') {
$this->_mime = $this->_requestBody;
$this->_saveInSent = $this->_requestParameters['SaveInSent'] == 'T';
$this->_collectionId = $this->_requestParameters['CollectionId'];
$this->_itemId = $this->_requestParameters['ItemId'];
} else {
$xml = simplexml_import_dom($this->_requestBody);
$this->_mime = (string) $xml->Mime;
- $this->_saveInSent = isset($xml->SaveinSentItems);
+ $this->_saveInSent = isset($xml->SaveInSentItems);
if (isset ($xml->Source)) {
$this->_collectionId = (string)$xml->Source->FolderId;
$this->_itemId = (string)$xml->Source->ItemId;
}
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " saveInSent: " . (int)$this->_saveInSent);
}
/**
* this function generates the response for the client
*
* @return void
*/
public function getResponse()
{
$dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_EMAIL, $this->_device, $this->_syncTimeStamp);
$dataController->sendEmail($this->_mime, $this->_saveInSent);
}
}
diff --git a/lib/kolab_sync_backend.php b/lib/kolab_sync_backend.php
index 8511a0d..cdfc8f7 100644
--- a/lib/kolab_sync_backend.php
+++ b/lib/kolab_sync_backend.php
@@ -1,712 +1,897 @@
<?php
/**
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class kolab_sync_backend
{
/**
* Singleton instace of kolab_sync_backend
*
* @var kolab_sync_backend
*/
static protected $instance;
protected $storage;
protected $folder_meta;
protected $folder_uids;
protected $root_meta;
static protected $types = array(
1 => '',
2 => 'mail.inbox',
3 => 'mail.drafts',
4 => 'mail.wastebasket',
5 => 'mail.sentitems',
6 => 'mail.outbox',
7 => 'task.default',
8 => 'event.default',
9 => 'contact.default',
10 => 'note.default',
11 => 'journal.default',
12 => 'mail',
13 => 'event',
14 => 'contact',
15 => 'task',
16 => 'journal',
17 => 'note',
);
static protected $classes = array(
Syncroton_Data_Factory::CLASS_CALENDAR => 'event',
Syncroton_Data_Factory::CLASS_CONTACTS => 'contact',
Syncroton_Data_Factory::CLASS_EMAIL => 'mail',
Syncroton_Data_Factory::CLASS_TASKS => 'task',
);
const ROOT_MAILBOX = 'INBOX';
// const ROOT_MAILBOX = '';
const ASYNC_KEY = '/private/vendor/kolab/activesync';
const UID_KEY = '/shared/vendor/cmu/cyrus-imapd/uniqueid';
/**
* This implements the 'singleton' design pattern
*
* @return kolab_sync_backend The one and only instance
*/
static function get_instance()
{
if (!self::$instance) {
self::$instance = new kolab_sync_backend;
self::$instance->startup(); // init AFTER object was linked with self::$instance
}
return self::$instance;
}
/**
* Class initialization
*/
public function startup()
{
$this->storage = rcube::get_instance()->get_storage();
// @TODO: reset cache? if we do this for every request the cache would be useless
// There's no session here
//$this->storage->clear_cache('mailboxes.', true);
// set additional header used by libkolab
$this->storage->set_options(array(
// @TODO: there can be Roundcube plugins defining additional headers,
// we maybe would need to add them here
'fetch_headers' => 'X-KOLAB-TYPE X-KOLAB-MIME-VERSION',
'skip_deleted' => true,
'threading' => false,
));
// Disable paging
$this->storage->set_pagesize(999999);
}
/**
* List known devices
*
* @return array Device list as hash array
*/
public function devices_list()
{
if ($this->root_meta === null) {
// @TODO: consider server annotation instead of INBOX
if ($meta = $this->storage->get_metadata(self::ROOT_MAILBOX, self::ASYNC_KEY)) {
$this->root_meta = $this->unserialize_metadata($meta[self::ROOT_MAILBOX][self::ASYNC_KEY]);
}
else {
$this->root_meta = array();
}
}
if (!empty($this->root_meta['DEVICE']) && is_array($this->root_meta['DEVICE'])) {
return $this->root_meta['DEVICE'];
}
return array();
}
/**
* Get list of folders available for sync
*
* @param string $deviceid Device identifier
* @param string $type Folder type
*
* @return array List of mailbox folders
*/
public function folders_list($deviceid, $type)
{
$folders_list = array();
// get all folders of specified type
$folders = (array) kolab_storage::list_folders('', '*', $type, false, $typedata);
// get folders activesync config
$folderdata = $this->folder_meta();
// check if folders are "subscribed" for activesync
foreach ($folderdata as $folder => $meta) {
if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
|| empty($meta['FOLDER'][$deviceid]['S'])
) {
continue;
}
if (!empty($type) && !in_array($folder, $folders)) {
continue;
}
// Activesync folder identifier (serverId)
$folder_id = self::folder_id($folder, $typedata[$folder]);
$folders_list[$folder_id] = $this->folder_data($folder, $typedata[$folder]);
}
return $folders_list;
}
/**
* Getter for folder metadata
*
* @return array Hash array with meta data for each folder
*/
public function folder_meta()
{
if (!isset($this->folder_meta)) {
$this->folder_meta = array();
// get folders activesync config
$folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY);
foreach ($folderdata as $folder => $meta) {
if ($asyncdata = $meta[self::ASYNC_KEY]) {
if ($metadata = $this->unserialize_metadata($asyncdata)) {
$this->folder_meta[$folder] = $metadata;
}
}
}
}
return $this->folder_meta;
}
/**
* Creates folder and subscribes to the device
*
* @param string $name Folder name (UTF7-IMAP)
* @param int $type Folder (ActiveSync) type
* @param string $deviceid Device identifier
*
* @return bool True on success, False on failure
*/
public function folder_create($name, $type, $deviceid)
{
if ($this->storage->folder_exists($name)) {
$created = true;
}
else {
$type = self::type_activesync2kolab($type);
$created = kolab_storage::folder_create($name, $kolab_type);
}
if ($created) {
// Set ActiveSync subscription flag
$this->folder_set($name, $deviceid, 1);
return true;
}
return false;
}
/**
* Renames a folder
*
* @param string $old_name Old folder name (UTF7-IMAP)
* @param string $new_name New folder name (UTF7-IMAP)
* @param int $type Folder (ActiveSync) type
* @param string $deviceid Device identifier
*
* @return bool True on success, False on failure
*/
public function folder_rename($old_name, $new_name, $type, $deviceid)
{
$type = self::type_activesync2kolab($type);
$moved = kolab_storage::folder_rename($old_name, $new_name);
if ($moved) {
// UnSet ActiveSync subscription flag
$this->folder_set($old_name, $deviceid, 0);
// Set ActiveSync subscription flag
$this->folder_set($new_name, $deviceid, 1);
return true;
}
return false;
}
/**
* Deletes folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $deviceid Device identifier
*
*/
public function folder_delete($name, $deviceid)
{
unset($this->folder_meta[$name]);
return kolab_storage::folder_delete($name);
}
/**
* Sets ActiveSync subscription flag on a folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $deviceid Device identifier
* @param int $flag Flag value (0|1|2)
*/
public function folder_set($name, $deviceid, $flag)
{
// get folders activesync config
$metadata = $this->folder_meta();
$metadata = $metadata[$name];
if ($flag) {
if (empty($metadata)) {
$metadata = array();
}
if (empty($metadata['FOLDER'])) {
$metadata['FOLDER'] = array();
}
if (empty($metadata['FOLDER'][$deviceid])) {
$metadata['FOLDER'][$deviceid] = array();
}
// Z-Push uses:
// 1 - synchronize, no alarms
// 2 - synchronize with alarms
$metadata['FOLDER'][$deviceid]['S'] = $flag;
}
if (!$flag) {
unset($metadata['FOLDER'][$deviceid]['S']);
if (empty($metadata['FOLDER'][$deviceid])) {
unset($metadata['FOLDER'][$deviceid]);
}
if (empty($metadata['FOLDER'])) {
unset($metadata['FOLDER']);
}
if (empty($metadata)) {
$metadata = null;
}
}
// Return if nothing's been changed
if (!self::data_array_diff($this->folder_meta[$name], $metadata)) {
return true;
}
$this->folder_meta[$name] = $metadata;
return $this->storage->set_metadata($name, array(
self::ASYNC_KEY => $this->serialize_metadata($metadata)));
}
public function device_get($id)
{
$devices_list = $this->devices_list();
$result = $devices_list[$id];
return $result;
}
/**
* Registers new device on server
*
* @param array $device Device data
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public function device_create($device, $id)
{
// Fill local cache
$this->devices_list();
// Old Kolab_ZPush device parameters
// MODE: -1 | 0 | 1 (not set | flatmode | foldermode)
// TYPE: device type string
// ALIAS: user-friendly device name
// Syncroton (kolab_sync_backend_device) uses
// ID: internal identifier in syncroton database
// TYPE: device type string
$metadata = $this->root_meta;
$metadata['DEVICE'][$id] = $device;
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($metadata));
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// Update local cache
$this->root_meta['DEVICE'][$id] = $device;
// Subscribe to default folders
$foldertypes = $this->storage->get_metadata('*', kolab_storage::CTYPE_KEY);
$folders = array('INBOX');
$types = array(
'mail.drafts',
'mail.wastebasket',
'mail.sentitems',
'mail.outbox',
'event.default',
'contact.default',
);
$foldertypes = array_map('implode', $foldertypes);
$foldertypes = array_intersect($foldertypes, $types);
// get default folders
foreach ($foldertypes as $folder => $type) {
// only personal folders
if ($this->storage->folder_namespace($folder) == 'personal') {
$this->folder_set($folder, $id, 1);
}
}
}
return $result;
}
public function device_update($device, $id)
{
$devices_list = $this->devices_list();
$old_device = $devices_list[$id];
if (!$old_device) {
return false;
}
// Do nothing if nothing is changed
if (!self::data_array_diff($old_device, $device)) {
return true;
}
$device = array_merge($old_device, $device);
$metadata = $this->root_meta;
$metadata['DEVICE'][$id] = $device;
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($metadata));
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// Update local cache
$this->root_meta['DEVICE'][$id] = $device;
}
return $result;
}
/**
* Device delete.
*
* @param string $id Device ID
*
* @return bool True on success, False on failure
*/
public function device_delete($id)
{
$device = $this->device_get($id);
if (!$device) {
return false;
}
unset($this->root_meta['DEVICE'][$id], $this->root_meta['FOLDER'][$id]);
if (empty($this->root_meta['DEVICE'])) {
unset($this->root_meta['DEVICE']);
}
if (empty($this->root_meta['FOLDER'])) {
unset($this->root_meta['FOLDER']);
}
$metadata = $this->serialize_metadata($this->root_meta);
$metadata = array(self::ASYNC_KEY => $metadata);
// update meta data
$result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
if ($result) {
// remove device annotation for every folder
foreach ($this->folder_meta() as $folder => $meta) {
// skip root folder (already handled above)
if ($folder == self::ROOT_MAILBOX)
continue;
if (!empty($meta['FOLDER']) && isset($meta['FOLDER'][$id])) {
unset($meta['FOLDER'][$id]);
if (empty($meta['FOLDER'])) {
unset($this->folder_meta[$folder]['FOLDER']);
unset($meta['FOLDER']);
}
if (empty($meta)) {
unset($this->folder_meta[$folder]);
$meta = null;
}
$metadata = array(self::ASYNC_KEY => $this->serialize_metadata($meta));
$res = $this->storage->set_metadata($folder, $metadata);
if ($res && $meta) {
$this->folder_meta[$folder] = $meta;
}
}
}
}
return $result;
}
/**
* Helper method to decode saved IMAP metadata
*/
private function unserialize_metadata($str)
{
if (!empty($str)) {
// Support old Z-Push annotation format
if ($str[0] != '{') {
$str = base64_decode($str);
}
$data = json_decode($str, true);
return $data;
}
return null;
}
/**
* Helper method to encode IMAP metadata for saving
*/
private function serialize_metadata($data)
{
if (!empty($data) && is_array($data)) {
$data = json_encode($data);
// $data = base64_encode($data);
return $data;
}
return null;
}
/**
* Returns Kolab folder type for specified ActiveSync type ID
*/
public static function type_activesync2kolab($type)
{
if (!empty(self::$types[$type])) {
return self::$types[$type];
}
return '';
}
/**
* Returns ActiveSync folder type for specified Kolab type
*/
public static function type_kolab2activesync($type)
{
if ($key = array_search($type, self::$types)) {
return $key;
}
return key(self::$types);
}
/**
* Returns Kolab folder type for specified ActiveSync class name
*/
public static function class_activesync2kolab($class)
{
if (!empty(self::$classes[$class])) {
return self::$classes[$class];
}
return '';
}
private function folder_data($folder, $type)
{
// Folder name parameters
$delim = $this->storage->get_hierarchy_delimiter();
$items = explode($delim, $folder);
$name = array_pop($items);
// Folder UID
$folder_id = $this->folder_id($folder);
// Syncroton folder data array
return array(
'folderid' => $folder_id,
'parentid' => count($items) ? self::folder_id(implode($delim, $items)) : 0,
'displayname' => rcube_charset::convert($name, 'UTF7-IMAP', kolab_sync::CHARSET),
'type' => self::type_kolab2activesync($type),
);
}
/**
* Builds folder ID based on folder name
*/
public function folder_id($name, $type = null)
{
// ActiveSync expects folder identifiers to be max.64 characters
// So we can't use just folder name
if ($name === '' || !is_string($name)) {
return null;
}
if (isset($this->folder_uids[$name])) {
return $this->folder_uids[$name];
}
/*
@TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves.
There's one inconvenience of this solution: folder name/type change
would be handled in ActiveSync as delete + create.
// get folders unique identifier
$folderdata = $this->storage->get_metadata($name, self::UID_KEY);
if ($folderdata && !empty($folderdata[$name])) {
$uid = $folderdata[$name][self::UID_KEY];
$this->folder_uids[$name] = $uid;
return $uid;
}
*/
// Add type to folder UID hash, so type change can be detected by Syncroton
if (!$type) {
$metadata = $this->storage->get_metadata($name, kolab_storage::CTYPE_KEY);
$type = $metadata[$name][kolab_storage::CTYPE_KEY];
}
$uid = md5($name . '!!' . $type);
return $this->folder_uids[$name] = $uid;
return $uid;
}
/**
* Returns IMAP folder name
*
* @param string $id Folder identifier
* @param string $deviceid Device dentifier
*
* @return string Folder name (UTF7-IMAP)
*/
public function folder_id2name($id, $deviceid)
{
// check in cache first
if (!empty($this->folder_uids)) {
if (($name = array_search($id, $this->folder_uids)) !== false) {
return $name;
}
}
/*
@TODO: see folder_id()
// get folders unique identifier
$folderdata = $this->storage->get_metadata('*', self::UID_KEY);
foreach ((array)$folderdata as $folder => $data) {
if (!empty($data[self::UID_KEY])) {
$uid = $data[self::UID_KEY];
$this->folder_uids[$folder] = $uid;
if ($uid == $id) {
$name = $folder;
}
}
}
*/
// get all folders of specified type
$folderdata = $this->folder_meta();
// check if folders are "subscribed" for activesync
foreach ($folderdata as $folder => $meta) {
if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
|| empty($meta['FOLDER'][$deviceid]['S'])
) {
continue;
}
$uid = self::folder_id($folder);
$this->folder_uids[$folder] = $uid;
if ($uid == $id) {
$name = $folder;
}
}
return $name;
}
/**
* Compares two arrays
*
* @param array $array1
* @param array $array2
*
* @return bool True if arrays differs, False otherwise
*/
private static function data_array_diff($array1, $array2)
{
if (!is_array($array1) || !is_array($array2)) {
return $array1 != $array2;
}
if (count($array1) != count($array2)) {
return true;
}
foreach ($array1 as $key => $val) {
if (!array_key_exists($key, $array2)) {
return true;
}
if ($val !== $array2[$key]) {
return true;
}
}
return false;
}
+
+ /**
+ * Send the given message using the configured method.
+ *
+ * @param string $message Complete message source
+ * @param array $smtp_error SMTP error array (reference)
+ * @param array $smtp_opts SMTP options (e.g. DSN request)
+ *
+ * @return boolean Send status.
+ */
+ public function send_message(&$message, &$smtp_error, $smtp_opts = null)
+ {
+ $rcube = rcube::get_instance();
+
+ list($headers, $message) = preg_split('/\r?\n\r?\n/', $message, 2, PREG_SPLIT_NO_EMPTY);
+
+ // Parse headers to get sender and recipients
+ $headers = str_replace("\r\n", "\n", $headers);
+ $headers = explode("\n", trim($headers));
+
+ $ln = 0;
+ $lines = array();
+
+ foreach ($headers as $line) {
+ if (ord($line[0]) <= 32) {
+ $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($line);
+ }
+ else {
+ $lines[++$ln] = trim($line);
+ }
+ }
+
+ $headers = array();
+ $headers_map = array(
+ 'subject' => 'Subject',
+ 'from' => 'From',
+ 'to' => 'To',
+ 'cc' => 'Cc',
+ 'bcc' => 'Bcc',
+ );
+
+ foreach ($lines as $line) {
+ list($field, $string) = explode(':', $line, 2);
+ $_field = strtolower($field);
+
+ if (isset($headers_map[$_field])) {
+ $field = $headers_map[$_field];
+ }
+
+ $headers[$field] = trim($string);
+ }
+
+ $mailto = $headers['To'];
+
+ $headers['User-Agent'] .= $rcube->app_name;
+ if ($agent = $rcube->config->get('useragent')) {
+ $headers['User-Agent'] .= '/' . $agent;
+ }
+
+ // send thru SMTP server using custom SMTP library
+ if ($rcube->config->get('smtp_server')) {
+ $smtp_headers = $headers;
+ // generate list of recipients
+ $recipients = array();
+
+ if (!empty($headers['To']))
+ $recipients[] = $headers['To'];
+ if (!empty($headers['Cc']))
+ $recipients[] = $headers['Cc'];
+ if (!empty($headers['Bcc']))
+ $recipients[] = $headers['Bcc'];
+
+ // remove Bcc header
+ unset($smtp_headers['Bcc']);
+
+ // send message
+ if (!is_object($rcube->smtp)) {
+ $rcube->smtp_init(true);
+ }
+
+ $sent = $rcube->smtp->send_mail($headers['From'], $recipients, $smtp_headers, $message, $smtp_opts);
+ $smtp_response = $rcube->smtp->get_response();
+ $smtp_error = $rcube->smtp->get_error();
+
+ // log error
+ if (!$sent) {
+ rcube::raise_error(array('code' => 800, 'type' => 'smtp',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => "SMTP error: ".join("\n", $smtp_response)), true, false);
+ }
+ }
+ // send mail using PHP's mail() function
+ else {
+ $mail_headers = $headers;
+ $delim = $rcube->config->header_delimiter();
+ $subject = $headers['Subject'];
+ $to = $headers['To'];
+
+ // unset some headers because they will be added by the mail() function
+ unset($mail_headers['To'], $mail_headers['Subject']);
+
+ // #1485779
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ if (preg_match_all('/<([^@]+@[^>]+)>/', $to, $m)) {
+ $to = implode(', ', $m[1]);
+ }
+ }
+
+ foreach ($mail_headers as $header => $header_value) {
+ $mail_headers[$header] = $header . ': ' . $header_value;
+ }
+ $header_str = rtrim(implode("\r\n", $mail_headers));
+
+ if ($delim != "\r\n") {
+ $header_str = str_replace("\r\n", $delim, $header_str);
+ $msg_body = str_replace("\r\n", $delim, $message);
+ $to = str_replace("\r\n", $delim, $to);
+ $subject = str_replace("\r\n", $delim, $subject);
+ }
+
+ if (ini_get('safe_mode'))
+ $sent = mail($to, $subject, $message, $header_str);
+ else
+ $sent = mail($to, $subject, $message, $header_str, "-f$from");
+ }
+
+ if ($sent) {
+ $rcube->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $message));
+
+ // remove MDN headers after sending
+ unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
+
+ // get all recipients
+ if ($headers['Cc'])
+ $mailto .= ' ' . $headers['Cc'];
+ if ($headers['Bcc'])
+ $mailto .= ' ' . $headers['Bcc'];
+ if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
+ $mailto = implode(', ', array_unique($m[1]));
+
+ if ($rcube->config->get('smtp_log')) {
+ rcube::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
+ $rcube->get_user_name(),
+ $_SERVER['REMOTE_ADDR'],
+ $mailto,
+ !empty($smtp_response) ? join('; ', $smtp_response) : ''));
+ }
+ }
+
+ unset($headers['Bcc']);
+
+ // Build the message back
+ foreach ($headers as $header => $header_value) {
+ $headers[$header] = $header . ': ' . $header_value;
+ }
+ $message = trim(implode("\r\n", $headers)) . "\r\n\r\n" . ltrim($message);
+
+ return $sent;
+ }
+
+
+ /**
+ * Unique Message-ID generator.
+ *
+ * @return string Message-ID
+ */
+/*
+ public function gen_message_id()
+ {
+ $local_part = md5(uniqid('rcmail'.mt_rand(),true));
+// $domain_part = $this->user->get_username('domain');
+
+ // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
+ if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
+ foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
+ $host = preg_replace('/:[0-9]+$/', '', $host);
+ if ($host && preg_match('/\.[a-z]+$/i', $host)) {
+ $domain_part = $host;
+ }
+ }
+ }
+
+ return sprintf('<%s@%s>', $local_part, $domain_part);
+ }
+*/
}
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index 70d91d5..2cc1ba1 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -1,886 +1,916 @@
<?php
/**
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
*
*/
class kolab_sync_data_email extends kolab_sync_data
{
/**
* Mapping from ActiveSync Email namespace fields
*/
protected $mapping = array(
'Cc' => 'cc',
//'ContentClass' => 'contentclass',
'DateReceived' => 'internaldate',
//'DisplayTo' => 'displayto', //?
//'Flag' => 'flag',
//'FlagType' => 'flagtype', // child of Flag element
'From' => 'from',
//'Importance' => 'importance',
'InternetCPID' => 'charset',
//'MessageClass' => 'messageclass',
'ReplyTo' => 'replyto',
//'Read' => 'read',
'Subject' => 'subject',
//'ThreadTopic' => 'threadtopic',
'To' => 'to',
);
/**
* Kolab object type
*
* @var string
*/
protected $modelName = 'mail';
/**
* Type of the default folder
*
* @var int
*/
protected $defaultFolderType = Syncroton_Command_FolderSync::FOLDERTYPE_INBOX;
/**
* Default container for new entries
*
* @var string
*/
protected $defaultFolder = 'INBOX';
/**
* Type of user created folders
*
* @var int
*/
protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED;
/**
* Default namespace
*
* @var string
*/
protected $defaultNS = 'Email';
/**
* Field to sort search results by
*
* @var string
*/
protected $sortField = 'n_fileas';
/**
* the constructor
*
* @param Syncroton_Model_IDevice $device
* @param DateTime $syncTimeStamp
*/
public function __construct(Syncroton_Model_IDevice $device, DateTime $syncTimeStamp)
{
parent::__construct($device, $syncTimeStamp);
$this->storage = rcube::get_instance()->get_storage();
}
/**
* Creates model object
*
* @param Syncroton_Model_SyncCollection $collection Collection data
* @param string $serverId Local entry identifier
*
* @return Syncroton_Model_Email Email object
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
$message = $this->getObject($serverId);
if (empty($message)) {
// @TODO: exception
return;
}
$msg = $this->parseMessageId($serverId);
$headers = $message->headers; // rcube_message_header
// Calendar namespace fields
foreach ($this->mapping as $key => $name) {
$value = null;
switch ($name) {
case 'internaldate':
$value = self::date_from_kolab(rcube_imap_generic::strToTime($headers->internaldate));
break;
case 'cc':
case 'to':
case 'replyto':
case 'from':
$addresses = rcube_mime::decode_address_list($headers->$name, null, true, $headers->charset);
foreach ($addresses as $idx => $part) {
$name = $part['name'];
$mailto = $part['mailto'];
$string = $part['string'];
// @TODO: convert to utf8?
// @FIXME: set name + address or address only?
//rcube_utils::idn_to_utf8();
$addresses[$idx] = format_email_recipient($mailto, $name);
}
$value = implode(',', $addresses);
break;
case 'subject':
$value = $headers->get('subject');
break;
case 'charset':
$value = self::charset_to_cp($headers->charset);
break;
}
if (empty($value) || is_array($value)) {
continue;
}
$result[$key] = $value;
}
// $result['ConversationId'] = 'FF68022058BD485996BE15F6F6D99320';
// $result['ConversationIndex'] = 'CA2CFA8A23';
// Read flag
$result['Read'] = intval(!empty($headers->flags['SEEN']));
// Flagged message
if (!empty($headers->flags['FLAGGED'])) {
// Use FollowUp flag which is used in Android when message is marked with a star
/*
+@TODO:
$result['Flag'] = array(
'FlagType' => 'FollowUp',
'Status' => 1,
);
*/
}
// Importance/Priority
if ($headers->priority) {
if ($headers->priority < 3) {
$result['importance'] = 2; // High
}
else if ($headers->priority > 3) {
$result['Importance'] = 0; // Low
}
}
// get truncation
$truncateAt = null;
$opts = $collection->options;
$prefs = $opts['bodyPreferences'];
if ($opts['mimeSupport'] == 2 && ($this->asversion <= 12 || !empty($prefs[4]))) {
if (!empty($prefs[4]) && !empty($prefs[4]['truncationSize'])) {
$truncateAt = $prefs[4]['truncationSize'];
}
$airSyncBaseType = 4;
}
else if (!empty($prefs[2])) {
if (!empty($prefs[2]['truncationSize'])) {
$truncateAt = $prefs[2]['truncationSize'];
}
$airSyncBaseType = 2;
}
else {
if (!empty($prefs[1]) && !empty($prefs[1]['truncationSize'])) {
$truncateAt = $prefs[1]['truncationSize'];
}
$airSyncBaseType = 1;
}
if (isset($opts['mimeTruncation']) && $opts['mimeTruncation'] < 8) {
switch ($opts['mimeTruncation']) {
case Syncroton_Command_Sync::TRUNCATE_ALL:
$truncateAt = 0;
break;
case Syncroton_Command_Sync::TRUNCATE_4096:
$truncateAt = 4096;
break;
case Syncroton_Command_Sync::TRUNCATE_5120:
$truncateAt = 5120;
break;
case Syncroton_Command_Sync::TRUNCATE_7168:
$truncateAt = 7168;
break;
case Syncroton_Command_Sync::TRUNCATE_10240:
$truncateAt = 10240;
break;
case Syncroton_Command_Sync::TRUNCATE_20480:
$truncateAt = 20480;
break;
case Syncroton_Command_Sync::TRUNCATE_51200:
$truncateAt = 51200;
break;
case Syncroton_Command_Sync::TRUNCATE_102400:
$truncateAt = 102400;
break;
}
}
// Message body
if ($airSyncBaseType == 4) {
$messageBody = $this->storage->get_raw_body($message->uid);
/*
if ($this->asversion < 12) {
// if the email contains non 7bit ascii characters we can't transfer
// them via MIMEData xml and we need to fall back to plain text
if (preg_match('/[^\x00-\x7F]/', $messageBody)) {
$airSyncBaseType = 1;
$messageBody = $this->getMessageBody($message);
}
}
*/
}
else {
$messageBody = $this->getMessageBody($message, $airSyncBaseType == 2);
}
// strip out any non utf-8 characters
$messageBody = rcube_charset::clean($messageBody);
$real_length = $body_length = strlen($messageBody);
if ($truncateAt !== null && $body_length > $truncateAt) {
$messageBody = substr($messageBody, 0, $truncateAt);
$body_length = $truncateAt;
$isTruncacted = 1;
}
else {
$isTruncacted = 0;
}
if ($body_length > 0) {
$body_params = array(
'Type' => $airSyncBaseType,
'Truncated' => $isTruncacted,
);
if ($isTruncated) {
$body_params['EstimatedDataSize'] = $real_length;
}
$result['Body'] = $this->setBody($messageBody, $body_params);
/*
if ($this->asversion >= 12) {
$body = $domParrent->appendChild(new DOMElement('Body', null, 'uri:AirSyncBase'));
$body->appendChild(new DOMElement('Type', $airSyncBaseType, 'uri:AirSyncBase'));
$body->appendChild(new DOMElement('Truncated', $isTruncacted, 'uri:AirSyncBase'));
$body->appendChild(new DOMElement('EstimatedDataSize', $data->size, 'uri:AirSyncBase'));
$dataTag = $body->appendChild(new DOMElement('Data', null, 'uri:AirSyncBase'));
$dataTag->appendChild(new DOMText($messageBody));
}
else {
if ($airSyncBaseType == 4) {
$domParrent->appendChild(new DOMElement('MIMETruncated', $isTruncacted, 'uri:Email'));
$body = $domParrent->appendChild(new DOMElement('MIMEData', null, 'uri:Email'));
$body->appendChild(new DOMText($messageBody));
}
else {
$domParrent->appendChild(new DOMElement('BodyTruncated', $isTruncacted, 'uri:Email'));
$body = $domParrent->appendChild(new DOMElement('Body', null, 'uri:Email'));
$body->appendChild(new DOMText($messageBody));
}
}
*/
}
$result['NativeBodyType'] = intval($message->has_html_part(false));
// Message class
$result['MessageClass'] = 'IPM.Note' . ($airSyncBaseType == 4 ? '.SMIME' : '');
$result['ContentClass'] = 'urn:content-classes:message';
// attachments
$attachments = array_merge($message->attachments, $message->inline_parts);
if (!empty($attachments)) {
$result['Attachments'] = array();
foreach ($attachments as $attachment) {
$att = array();
$filename = $attachment->filename;
if (empty($filename) && $attachment->mimetype == 'text/html') {
$filename = 'HTML Part';
}
$att['DisplayName'] = $filename;
$att['FileReference'] = $serverId . '::' . $attachment->mime_id;
$att['Method'] = 1;
$att['EstimatedDataSize'] = $attachment->size;
if (!empty($attachment->content_id)) {
$att['ContentId'] = $attachment->content_id;
}
if (!empty($attachment->content_location)) {
$att['ContentLocation'] = $attachment->content_location;
}
if (in_array($attachment, $message->inline_parts)) {
$att['IsInline'] = 1;
}
$result['Attachments'][] = new Syncroton_Model_EmailAttachment($att);
}
}
// @TODO: flags
return new Syncroton_Model_Email($result);
}
/**
* convert contact from xml to libkolab array
*
* @param Syncroton_Model_IEntry $data Contact to convert
* @param string $folderid Folder identifier
* @param array $entry Existing entry
*
* @return array
*/
public function toKolab(Syncroton_Model_IEntry $data, $folderid, $entry = null)
{
// does nothing => you can't add emails via ActiveSync
}
-
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected function filter($filter_type = 0)
{
$filter = array();
switch ($filter_type) {
case Syncroton_Command_Sync::FILTER_1_DAY_BACK:
$mod = '-1 day';
break;
case Syncroton_Command_Sync::FILTER_3_DAYS_BACK:
$mod = '-3 days';
break;
case Syncroton_Command_Sync::FILTER_2_WEEKS_BACK:
$mod = '-2 weeks';
break;
case Syncroton_Command_Sync::FILTER_1_MONTH_BACK:
$mod = '-1 month';
break;
}
if (!empty($mod)) {
$dt = new DateTime('now', new DateTimeZone('UTC'));
$dt->modify($mod);
// RFC3501: IMAP SEARCH
$filter[] = 'SINCE ' . $dt->format('d-m-Y');
}
return $filter;
}
/**
* Returns default folder for current class type.
*/
protected function getDefaultFolder()
{
// There's always INBOX
$real_id = $this->backend->folder_id('INBOX', 'mail.inbox');
$folder_id = $this->defaultRootFolder;
return array(
'displayname' => 'Inbox',
'realid' => $real_id,
'folderid' => $folder_id,
'parentid' => 0,
'type' => $this->defaultFolderType,
);
}
public function moveItem($srcFolderId, $serverId, $dstFolderId)
{
$msg = $this->parseMessageId($serverId);
$dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid);
if ($dstname === null) {
return;
}
if (!$this->storage->move_message($msg['uid'], $dstname, $msg['foldername'])) {
return;
}
// Use COPYUID feature (RFC2359) to get the new UID of the copied message
$copyuid = $this->storage->conn->data['COPYUID'];
if (is_array($copyuid) && ($uid = $copyuid[1])) {
return $uid;
}
}
/**
* add entry from xml data
*
* @param string $folderId
* @param Syncroton_Model_IEntry $entry
*
* @return array
*/
public function createEntry($folderId, Syncroton_Model_IEntry $entry)
{
}
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_IEntry $entry
* @return array
*/
public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
{
$msg = $this->parseMessageId($serverId);
$message = $this->getObject($serverId);
if (isset($entry->Read)) {
// here we update only Read flag
$flag = (((int)$entry->Read != 1) ? 'UN' : '') . 'SEEN';
$this->storage->set_flag($msg['uid'], $flag, $msg['foldername']);
}
$is_flagged = !empty($message->headers->flags['FLAGGED']);
// @TODO: Flag change
if (empty($entry->Flag)) {
if ($is_flagged) {
$this->storage->set_flag($msg['uid'], 'UNFLAGGED', $msg['foldername']);
}
}
else if (!$is_flagged) {
/*
if ($entry->Flag->FlagType && preg_match('/^follow\s*up/i', $entry->Flag->FlagType)) {
$this->storage->set_flag($msg['uid'], 'FLAGGED', $msg['foldername']);
}
*/
}
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_SyncCollection $collection
*/
public function deleteEntry($folderId, $serverId, $collection)
{
$trash = kolab_sync::get_instance()->config->get('trash_mbox');
$msg = $this->parseMessageId($serverId);
// move message to trash folder
if ($collection->deletesAsMoves
&& strlen($trash)
&& $trash != $msg['foldername']
&& $this->storage->folder_exists($trash)
) {
$this->storage->move_message($msg['uid'], $trash, $msg['foldername']);
}
// set delete flag
else {
$this->storage->set_flag($msg['uid'], 'DELETED', $msg['foldername']);
}
}
- public function sendEmail($inputStream, $saveInSent)
+ public function sendEmail($body, $saveInSent)
{
- // @TODO: not implemented
+ $sent = $this->backend->send_message($body, $smtp_error);
+
+ if ($sent && $saveInSent) {
+ $this->saveInSent($body);
+ }
}
- public function forwardEmail($collectionId, $itemId, $inputStream, $saveInSent)
+ public function forwardEmail($collectionId, $itemId, $body, $saveInSent)
{
// @TODO: not implemented
+
+ if ($sent && $saveInSent) {
+ $this->saveInSent($body);
+ }
}
- public function replyEmail($collectionId, $itemId, $inputStream, $saveInSent)
+ public function replyEmail($collectionId, $itemId, $body, $saveInSent)
{
// @TODO: not implemented
+
+ if ($sent && $saveInSent) {
+ $this->saveInSent($body);
+ }
+ }
+
+ /**
+ * Append the message into Sent folder
+ *
+ * @param string $message Complete message source
+ *
+ * @return int|bool Appended message UID or True on success, False on error
+ */
+ protected function saveInSent($message)
+ {
+ $sent_folder = kolab_sync::get_instance()->config->get('sent_mbox');
+
+ if (strlen($sent_folder) && $this->storage->folder_exists($sent_folder)) {
+ return $this->storage->save_message($sent_folder, $message);
+ }
+
+ return false;
}
/**
* Search for existing entries
*
* @param string $folderid
* @param array $filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
public function search($folderid, $filter = array(), $result_type = self::RESULT_UID)
{
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
$filter_str = 'ALL UNDELETED';
// convert filter into one IMAP search string
foreach ($filter as $idx => $filter_item) {
if (is_array($filter_item)) {
// @TODO
// convert 'changed' entries into IMAP search string
// for now we just return empty result
return $result_type == self::RESULT_COUNT ? 0 : array();
}
else {
$filter_str .= ' ' . $filter_item;
}
}
$result = $result_type == self::RESULT_COUNT ? 0 : array();
// no sorting for best performance
$sort_by = null;
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
// $this->storage->set_folder($foldername);
$this->storage->folder_sync($foldername);
$this->storage->search($foldername, $filter_str, RCMAIL_CHARSET, $sort_by);
$search = $this->storage->get_search_set();
$search = $search[1];
if (!($search instanceof rcube_result_index)) {
continue;
}
switch ($result_type) {
case self::RESULT_COUNT:
$result += (int) $search->count();
break;
case self::RESULT_UID:
if ($uids = $search->get()) {
foreach ($uids as $idx => $uid) {
$uids[$idx] = $this->createMessageId($folderid, $uid);
}
$result = array_merge($result, $uids);
}
break;
/*
case self::RESULT_OBJECT:
default:
if ($objects = $folder->select($filter)) {
$result = array_merge($result, $objects);
}
*/
}
}
return $result;
}
/**
* Fetches the entry from the backend
*/
protected function getObject($entryid, &$folder = null)
{
$message = $this->parseMessageId($entryid);
if (empty($message)) {
// @TODO: exception?
return null;
}
// set current folder
$this->storage->set_folder($message['foldername']);
// get message
$message = new rcube_message($message['uid']);
return $message;
}
/**
* @return Syncroton_Model_FileReference
*/
public function getFileReference($fileReference)
{
list($folderid, $uid, $part_id) = explode('::', $fileReference);
$message = $this->getObject($fileReference);
$part = $message->mime_parts[$part_id];
$body = $message->get_part_content($part_id);
$content_type = $part->mimetype;
/*
<ItemOperations xmlns="uri:ItemOperations" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:AirSync="uri:AirSync" xmlns:ItemOperations="uri:ItemOp
ions"><Status>1</Status><Response><Fetch><Status>1</Status><AirSyncBase:FileReference>38b950ebd62cd9a66929c89615d0fc04::3::2</AirSyncBase:F
eference><Properties><AirSyncBase:ContentType xmlns="uri:AirSyncBase">text/x-diff</AirSyncBase:ContentType></Properties></Fetch></Response>
emOperations>
}
*/
return new Syncroton_Model_FileReference(array(
'ContentType' => $content_type,
'Data' => $body,
));
}
/**
* Parses entry ID to get folder name and UID of the message
*/
protected function parseMessageId($entryid)
{
list($folderid, $uid) = explode('::', $entryid);
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null || $foldername === false) {
// @TODO exception?
return null;
}
return array(
'uid' => $uid,
'folderid' => $folderid,
'foldername' => $foldername,
);
}
/**
* Creates entry ID of the message
*/
public function createMessageId($folderid, $uid)
{
return $folderid . '::' . $uid;
}
/**
* Returns body of the message in specified format
*/
protected function getMessageBody($message, $html = false)
{
if (!is_array($message->parts) && empty($message->body)) {
return '';
}
if (!empty($message->parts)) {
foreach ($message->parts as $part) {
// skip no-content and attachment parts (#1488557)
if ($part->type != 'content' || !$part->size || $message->is_attachment($part)) {
continue;
}
return $this->getMessagePartBody($message, $part, $html);
}
}
return $this->getMessagePartBody($message, $message, $html);
}
/**
* Returns body of the message part in specified format
*/
protected function getMessagePartBody($message, $part, $html = false)
{
// Check if we have enough memory to handle the message in it
// @FIXME: we need up to 5x more memory than the body
if (!rcube_utils::mem_check($part->size * 5)) {
return '';
}
if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset'])) {
$part->ctype_parameters['charset'] = $message->headers->charset;
}
// fetch part if not available
if (!isset($part->body)) {
$part->body = $message->get_part_content($part->mime_id);
}
// message is cached but not exists, or other error
if ($part->body === false) {
return '';
}
$body = $part->body;
if ($html) {
if ($part->ctype_secondary == 'html') {
}
else {
$body = '<pre>' . $body . '</pre>';
}
}
else {
if ($part->ctype_secondary == 'html') {
$txt = new html2text($body, false, true);
$body = $txt->get_text();
}
else {
if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
$body = rcube_mime::unfold_flowed($body);
}
}
}
return $body;
}
public static function charset_to_cp($charset)
{
// @TODO: ?????
// The body is converted to utf-8 in get_part_content(), what about headers?
return 65001; // UTF-8
$aliases = array(
'asmo708' => 708,
'shiftjis' => 932,
'gb2312' => 936,
'ksc56011987' => 949,
'big5' => 950,
'utf16' => 1200,
'utf16le' => 1200,
'unicodefffe' => 1201,
'utf16be' => 1201,
'johab' => 1361,
'macintosh' => 10000,
'macjapanese' => 10001,
'macchinesetrad' => 10002,
'mackorean' => 10003,
'macarabic' => 10004,
'machebrew' => 10005,
'macgreek' => 10006,
'maccyrillic' => 10007,
'macchinesesimp' => 10008,
'macromanian' => 10010,
'macukrainian' => 10017,
'macthai' => 10021,
'macce' => 10029,
'macicelandic' => 10079,
'macturkish' => 10081,
'maccroatian' => 10082,
'utf32' => 12000,
'utf32be' => 12001,
'chinesecns' => 20000,
'chineseeten' => 20002,
'ia5' => 20105,
'ia5german' => 20106,
'ia5swedish' => 20107,
'ia5norwegian' => 20108,
'usascii' => 20127,
'ibm273' => 20273,
'ibm277' => 20277,
'ibm278' => 20278,
'ibm280' => 20280,
'ibm284' => 20284,
'ibm285' => 20285,
'ibm290' => 20290,
'ibm297' => 20297,
'ibm420' => 20420,
'ibm423' => 20423,
'ibm424' => 20424,
'ebcdickoreanextended' => 20833,
'ibmthai' => 20838,
'koi8r' => 20866,
'ibm871' => 20871,
'ibm880' => 20880,
'ibm905' => 20905,
'ibm00924' => 20924,
'cp1025' => 21025,
'koi8u' => 21866,
'iso88591' => 28591,
'iso88592' => 28592,
'iso88593' => 28593,
'iso88594' => 28594,
'iso88595' => 28595,
'iso88596' => 28596,
'iso88597' => 28597,
'iso88598' => 28598,
'iso88599' => 28599,
'iso885913' => 28603,
'iso885915' => 28605,
'xeuropa' => 29001,
'iso88598i' => 38598,
'iso2022jp' => 50220,
'csiso2022jp' => 50221,
'iso2022jp' => 50222,
'iso2022kr' => 50225,
'eucjp' => 51932,
'euccn' => 51936,
'euckr' => 51949,
'hzgb2312' => 52936,
'gb18030' => 54936,
'isciide' => 57002,
'isciibe' => 57003,
'isciita' => 57004,
'isciite' => 57005,
'isciias' => 57006,
'isciior' => 57007,
'isciika' => 57008,
'isciima' => 57009,
'isciigu' => 57010,
'isciipa' => 57011,
'utf7' => 65000,
'utf8' => 65001,
);
$charset = strtolower($charset);
$charset = preg_replace(array('/^x-/', '/[^a-z0-9]/'), '', $charset);
if (isset($aliases[$charset])) {
return $aliases[$charset];
}
if (preg_match('/^(ibm|dos|cp|windows|win)[0-9]+/', $charset, $m)) {
return substr($charset, strlen($m[1]) + 1);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Apr 19, 1:22 PM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
435781
Default Alt Text
(129 KB)
Attached To
Mode
R4 syncroton
Attached
Detach File
Event Timeline
Log In to Comment