Page MenuHomePhorge

No OneTemporary

Size
149 KB
Referenced Files
None
Subscribers
None
diff --git a/CHANGELOG b/CHANGELOG
index 21205e780..a4fe069d6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,567 +1,573 @@
CHANGELOG RoundCube Webmail
---------------------------
+2007/01/07 (estadtherr)
+----------
+- Fixed display of HTML message attachments (closes #1484178)
+
+
2007/01/07 (thomasb)
----------
- Applied patch for preview caching (closes #1484186)
- Added Thai and Vietnamese localization files
+
2006/12/29 (thomasb)
----------
- Added error handling for attachment uploads
- Use multibyte safe string functions where necessary (closes #1483988)
- Updated Swiss German localization (de_CH)
2006/12/22 (thomasb)
----------
- Applied security patch to validate the submitted host value (by Kees Cook)
- Applied security patch to validate input values when deleting contacts (by Kees Cook)
- Applied security patch that sanitizes emoticon paths when attaching them (by Kees Cook)
- Applied a patch to more aggressively sanitize a HTML message
- Visualize blocked images in HTML messages
2006/12/20 (thomasb)
----------
- Fixed wrong message listing when showing search results (closes #1484131)
- Introduced functions Q() and JQ() as aliases for rep_specialchars_output()
- Show remote images when opening HTML message part as attachment
2006/12/17 (thomasb)
----------
- Added patch by Ryan Rittenhouse & David Glick for a resizeable preview pane
2006/12/06 (thomasb)
----------
- Improve memory usage when sending mail (closes #1484098)
- Mark messages as read once the preview is loaded (closes #1484132)
- Include smtp final response in log (closes #1484081)
2006/12/04 (thomasb)
----------
- Corrected date string in sent message header (closes #1484125)
- Correclty choose "To" column in sent and draft mailboxes (closes #1483943)
- Changed srong tooltips for message browse buttons (closes #1483930)
2006/12/03 (estadtherr)
----------
- Added fix to convert HTML signatures for plain text messages
- Fixed signature delimeter character to be standard (Bug #1484035)
2006/12/01 (thomasb)
----------
- Implemented preview pane
- Fixed XSS vulnerability (Bug #1484109)
- Remove newlines from mail headers (Bug #1484031)
- Selection issues when moving/deleting (Bug #1484044)
- Applied patch of Clement Moulin for imap host auto-selection
- ISO-encode IMAP password for plaintext login (Bugs #1483977 & #1483886)
- Fixed folder name encoding in subscription list (Bug #1484113)
- Fixed JS errors in identity list (Bug #1484120)
- Show client debug console on debug_level 8
- Added Serbian translation
- Updated Spanish and Bulgarian localization
2006/11/22 (robin)
----------
- Fix a bug introduced with Shift-Del yesterday
2006/11/21 (robin)
----------
- Add missing nl_NL translations
- Translate foldernames in folder form (closes #1484113)
2006/11/21 (robin)
----------
- Added first and last buttons to message list, address book
and message detail
- Pressing Shift-Del bypasses Trash folder
- Enable purge command for Junk folder
2006/11/17 (robin)
----------
- Re-initialize message list after shift-select and delete
2006/11/16 (robin)
----------
- Fixed updating message list after expunge and purge
- Fetch all aliases if virtuser_query is used instead
of only the first one
2006/11/11 (estadtherr)
----------
- fixed deletion/moving of messages from within "show" page
2006/11/09 (thomasb)
----------
- Little bugfix in HTML encoding
- Fixed encoding issues and delete-on-reply problem
- Corrected template parsing
2006/11/07 (estadtherr)
----------
- Upgraded to TinyMCE v2.0.8
- Fixed CSS path for editor popups
2006/09/26 (estadtherr)
----------
- Added spellchecker plugin to TinyMCE configuration
- Fixed HTML/Plain toggle labels
2006/09/24 (thomasb)
----------
- Partial client re-write with a common list class
- Re-enabled multi select of contacts (Bug #1484017)
- Enable contact editing right after creation (Bug #1459641)
- Updated Hungarian, Estonian and Traditional Chinese localization
2006/09/19 (thomasb)
----------
- Correct UTF-7 to UTF-8 conversion if mbstring is not available
2006/09/13 (estadtherr)
----------
- Introduction of TinyMCE HTML editor support for message composition and signatures
Note : a new column is added to the "identities" database table
2006/09/12 (estadtherr)
----------
- Fixed html2text treatment of table headers (Bug #1484020)
- Fixed IMAP fetch of message body (Bug #1484019)
2006/09/08 (thomasb)
----------
- Fixed safe_mode problems (Bug #1418381)
- Fixed wrong header encoding (Bug #1483976)
2006/09/07 (thomasb)
----------
- Made automatic draft saving configurable
- Fixed JS bug when renaming folders (Bug #1483989)
- Don't wait for complete page load when calling JavaScript init()
- Some improvements to prevent session expiration
- Prevent from double submit of spell check requests
2006/09/01 (thomasb)
----------
- Imporoved message parsing and HTML validation
- Added quota display as image (by Brett Patterson)
- Corrected creation of a message-id
- Updated Norwegian (bokmal) localization
2006/08/30 (thomasb)
----------
- New indentation for quoted message text
- Improved HTML validity
2006/08/28 (estadtherr)
----------
- Fixed URL character set (Ticket #1445501)
- Fixed saving of contact into MySQL from LDAP query results (Ticket #1483820)
2006/08/25 (thomasb)
----------
- Fixed folder renaming: unsubscribe before rename (Bug #1483920)
- Finalized new message parsing (+ chaching)
- Updated SQL scripts and UPGRADING instructions
2006/08/23 (thomasb)
----------
- Updated Polish, Portuguese, Latvian, Chinese and Japanese localization
2006/08/20 (thomasb)
----------
- Fixed wrong usage of mbstring (Bug #1462439)
- Set default spelling language (Ticket #1483938)
- Added support for Nox Spell Server
2006/08/18 (thomasb)
----------
- Re-built message parsing (Bug #1327068)
Now based on the message structure delivered by the IMAP server.
- Fixed some XSS and SQL injection issues
2006/08/10 (thomasb)
----------
- Fixed charset problems with folder renaming
2006/08/04 (thomasb)
----------
- Fixed Bug in saving identities (Ticket #1483915)
- Set folder name in window title (Bug #1483919)
- Don't add imap_root to INBOX path (Bug #1483816)
- Attempt to create default folders only after login
- Avoid usage of $CONFIG in rcube_imap class
2006/07/30 (thomasb)
----------
- Alter links in HTML messages (Bug #1326402)
- Added fallback if host not found in 'mail_domain' array
- Applied patch of Charles to highlight droptargets (Ticket #1473034)
- Fixed folder renaming (Bug #1483914)
- Added confirmation message after deleting a folder
2006/07/25 (thomasb)
----------
- Made folder renaming a bit more ajax-style
- Changed rename-labels and German translation
- Fixed addressbox countbar width (Bug #1483845)
- Fixed refresh interval problems in Safari (Bug #1483902)
- Fixed clear_message_list_header() errors (Bug #1483898)
- Sanity check of $message_set in imap.inc (Bug #1443200)
- Added correct changing of message list headers for Sent folder
- Updated Spanish localization (Ticket #1483887)
- Applied patch #1483846
2006/07/24 (richs)
----------
- Draft window no longer reloads. It saves to an iframe in the background instead (fixes bug #1483869)
- Draft timer now part of program/js/app.js instead of skins/default/templates/compose.inc
- Draft saving now properly returns an error when saving fails
- Draft timer stops and resets properly when attachments are uploaded, or when saving manually
- Old compose session/attachments are now cleaned up when a new/forward/reply/draft is made/opened
2006/07/19 (thomasb)
----------
- Correct entity encoding of link urls (HTML validity)
- Improved usability in compose step (Ticket #1483807)
- Added absolute URLs to several buttons (for "open in new window")
- Applied patch #1328032
- Fixed Bug/Patch #1443200
2006/07/18 (thomasb)
----------
- Fixed password with spaces issue (Bug #1364122)
- Replaced _auth hash with second cookie (Ticket #1483811)
- Don't use get_input_value() for passwords (Bug #1468895)
- Made password encryption key configurable
- Minor bugfixes with charset encoding
- Added <label> tags to forms (Ticket #1483810)
2006/07/07 (thomasb)
----------
- Fixed INSTALL_PATH bug #1425663
2006/07/03 (richs)
----------
- Fixed compatibility with in-body email addresses containing "+" (Bug #1483836)
- Updated French localizations (Ticket #1483862)
- Incoming messages can now be moved to Drafts, edited, saved, then moved back (Feature #1436191)
- Added Firefox workaround when clicking whitespace to drag messages (Bug #1483857)
- Corrected Dutch and Italian localizations (Ticket #1483851 and #1483848)
- Enabled 'Empty' (purge) command for Junk mailbox (defined in main.inc.php)
2006/06/30 (richs)
----------
- Fixed empty INBOX compatibility bug (Patch #1443200)
- Temporarily fixed French "compact" localization (Patch #1483862)
- Fixed "Select All" not working with Delete interface button (Bug #1332434)
- Fixed messsage list column compatibility with Konqueror (Bug #1395711)
- Fixed "unread count" in window title when count changed (Bug #1483812)
- Fixed DB error when deleting from message table (Patch #1483835)
2006/06/29 (richs)
----------
- Added ability to remove attachments (Feature #1436721)
- Default folders are now auto-created on first login (Feature #1471594)
- Fixed compatibility with folder apostrophes (e.g.: Joe's Folder) (Bug #1429458)
- Corrected Italian localizations
- Tweaked rename-folder form to clear after a rename
2006/06/26 (richs)
----------
- Added button to immediately check for new messages
- New message checking now displays status "Checking for new messages..."
- New message checking now looks for unread messages in all mailboxes (Feature #1326401)
- Task buttons now respond to clicks by darkening (as in other applications)
- Fixed "Sender" column changing to "Recipient" for "Sent" and "Drafts" message lists
- Added ability to sort messages by "Size"
- Added ability to rename folders (Feature #1326396)
- Added 'protect_default_folders' option to main.inc.php to prevent renames/deletes/unsubscribes of default folders
- Corrected 5 typos of "INSTLL" to "INSTALL" in program/include/main.inc
2006/06/25
----------
- Changed behavior to include host-specific configuration (Bug #1483849)
- Assume ISO-8859-1 encoding of mail messages by default (Patch #1483839)
- Fixed spell checker to work with the new URL at google.com
- Some memory and security optimizations sendmail.inc
- Updated UGRADING description
2006/06/19
----------
- Added Drafts support (Feature #1326839) (richs)
2006/06/02
----------
- Updated Estonian localization and moved from ee to et
- Added Bulgarian localization
2006/05/25
----------
- Finalized GoogieSpell integration
2006/05/18
----------
- Added Arabic and Armenian localizations
- Updated Russian localization
- Removed MDB2 classes from repository. Install them seperately if used.
- Updated MDB2 wrapper class contributed by Lukas Kahwe Smith
- Allow & in e-mail addresses
2006/05/05
----------
- Fixed typos in function rcube_button() (Bugs #1473198 and #1473201)
- Check for zlib.output_compression before using ob_gzhandler (Bug #1471069)
- Casting date parts in iil_StrToTime() to avoid warnings (Bug #1482140)
- Corrected INSTALL description (Bug #1476106)
- Added charset to javascript HTTP headers
- Fixed Opera bug with CC and BCC fields (Bug #1474576)
- Changed login page title regarding product name (Bug #1476413)
- Pimped search function
- Applied attachment viewing/forwarding patches by Andrew Fladmark
- Applied prev/next patch by Leonard Bouchet
- Applied patches by Mark Bucciarelli
- Applied patch for requesting receipts by Salvatore Ansani
- Integrated GoogieSpell as suggested by phil (styling is not perfect yet, localization is missing)
2006/04/13
----------
- Added Slovenian localization
- Updated Portuguese localization
- Fixed parent.location problem for compose-links
- Added sort order saving patch by Jacob Brunson
- Added gzip compression support
2006/04/02
----------
- Added Lithuanian localization
- Improved search function
- Added version string as template object
- Load host-specific configuration file (see config/main.inc.php)
- New config parameter adding domain to user names for login
- Strip tags on _auth, _action, _task parameters
- Corrected labels for next/previous page buttons in address book
2006/03/23
----------
- Auto-detect mail header delimiters
- Regard daylight savings
- Localized quota display
- Started implementing search function
2006/03/20
----------
- Avoid error message when saving an unchanged identity (Bug #1429510)
- Fixed hard-coded cols selection for sent folder (Bug #1354586)
- Enable some HTML links for use with "open in new window" or "save target"
- Check meta-key instead of ctrl on Macs
- Ignore double clicks when holding down a modifier key
- Fixed reloading of the login page
- Fixed typo in compose template (Bug #1446852)
- Added compose button to message read step (Request #1433288)
- New config parameter for persistent database connections (Bug #1431817)
2006/03/14
----------
- Don't remove internal HTML tags in plaintext messages
- Improved error handling in DB connection failure
2006/02/22
----------
- Updated localizations
- Fixed bug #1435989
2006/02/19
----------
- Updated localizations
- Applied patch of Anders Karlsson
- Applied patch of Jacob Brunson
- Applied patch for virtuser_query by Robin Elfrink
- Added support for References header (patch by Auke)
- Added support for mbstring module by Tadashi Jokagi
- Added function for automatic remove of slashes on GET and POST vars
if magic_quotes is enabled
2006/02/05
----------
- Added Slovak, Hungarian, Bosnian and Croation translation
- Fixed bug when inserting signatures with !?&
- Chopping message headers before inserting into the message cache table
(to avoid bugs in Postgres)
- Allow one-char domains in e-mail addresses
- Make product name in page title configurable
- Make username available as skin object
- Added session_write_close() in rcube_db class destructor to avoid problems
in PHP 5.0.5
- Use move_uploaded_file() instead of copy() for a more secure handling of
uploaded attachments
- Additional config parameter to show/hide deleted messages
- Added periodic request for checking new mails (Request #1307821)
- Added EXPUNGE command
- Optimized loading time for mail interface
- Changed to full UTF-8 support
- Additional timezones (Patch #1389912)
- Added LDAP public search (experimental)
- Applied patch for correct ctrl/shift behavior for message selection (Bug #1326364)
- Casting to strings when adding empty headers to message cache (Bug #1406026)
- Skip sender-address as recipient when Reply-to-all
- Fixes in utf8-class
- Added patch for Quota display by Aury Fink Filho <nuny@aury.com.br>
- Added garbage collector for message cache
- Added patches for BCC headers
2005/12/16
----------
- Added Turkish and Simplified Chinese translation
- Use virtusertable to resolve e-mail addresses at login
- Configurable mail_domain used to compose correct e-mail addresses
on first login
2005/12/03
----------
- Added Finnish, Romanian, Polish, Czech, British, Norwegian, Greek, Russian,
Estonian and Chinese translation
- Get IMAP server capabilities in array
- Check for NAMESPACE capability before sending command
- Set default user language from config 'locale_string'
- Added sorting patch for message list
- Make default sort col/order configurable
- Fixed XSS in address book and identities
- Added more XSS protection (Bug #1308236)
- Added tab indexes for compose form
- Added 'changed' col to contacts table
- Support for 160-bit session hashes
- Added input check for contacts and identities (Patch #1346523)
- Added messages/warning to compose step (Patch #1323895)
- Added favicon to the default skin
- Fixed Bug #1334337 as far as possible
- Added Reply-To-All functionality (Request #1326395, Patch #1349777)
- Redesign of client side AJAX code (enable multi threading)
- Added keep-alive signal every minute
- Make logs dir configurable
- Added support for SMTPS
- Decode attachment file names
- Make delimiter for message headers configurable
- Add generic footer to sent messages
- Choose the rigt identity when replying
- Remove signature when replying (Request #1333167)
- Signatures for each identity
- Select charset when composing message
- Complete re-design of the caching mechanism
2005/08/11
----------
- Write list header to client even if list is empty
- Add functions "select all", "select none" to message list
- Improved filter for HTML messages to remove potentially malicious tags
(script, iframe, object) and event handlers.
- Buttons for next/previous message in view mode
- Add new created contact to list and show confirmation status
- Added folder management (subscribe/create/delete)
- Log message sending (SMTP log)
- Grant access for Camino browser
- Added German translation
2005/10/20
----------
- Added Swedish, Latvian, Portuguese and Catalan translation
- Make SMTP auth method configurable
- Make mailboxlist scrollable (Bug #1326372)
- Fixed SSL support
- Improved support for Courier IMAP (root folder and delimiter issues)
- Moved taskbar from bottom to top
- Added 'session_lifetime' parameter
- Fixed wrong unread count when deleting message (Bug #1332434)
- Srip tags when creating a new folder (Bug #1332084)
- Translate HTML tags in message headers (Bug #1330134)
- Correction in German translation (Bug #1329434)
- Display folder names with special chars correctly (Bug #1330157)
2005/10/07
----------
- Added French, Italian, Spanish, Danish, Dutch translation
- Clarified license (Bug #1305966)
- Fixed PHP warnings (Bug #1299403)
- Fixed english translation (Bug #1295406)
- Fixed bug #1290833: Last character of email not seen
- Fixed bug #1292199 when creating new user
- Allow more browsers (Bug #1285101)
- Added setting for showing pretty dates
- Added support for SQLite database
- Make use of message caching configurable
- Also add attachments when forwarding a message
- Long folder names will not flow over message list (Bug #1267232)
- Show nested mailboxes hieracically
- Enable IMAPS by host
2005/08/20
----------
- Improved cacheing of mailbox messagecount
- Fixed javascript bug when creating a new message folder
- Fixed javascript bugs #1260990 and #1260992: folder selection
- Make Trash folder configurable
- Auto create folders Inbox, Sent and Trash (if configured)
- Support for IMAP root folder
- Added support fot text/enriched messages
- Make list of special mailboxes configurable
diff --git a/program/include/rcube_imap.inc b/program/include/rcube_imap.inc
index d057e0ec1..544552696 100644
--- a/program/include/rcube_imap.inc
+++ b/program/include/rcube_imap.inc
@@ -1,2659 +1,2659 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_imap.inc |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2005-2006, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| IMAP wrapper that implements the Iloha IMAP Library (IIL) |
| See http://ilohamail.org/ for details |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
/**
* Obtain classes from the Iloha IMAP library
*/
require_once('lib/imap.inc');
require_once('lib/mime.inc');
/**
* Interface class for accessing an IMAP server
*
* This is a wrapper that implements the Iloha IMAP Library (IIL)
*
* @package RoundCube Webmail
* @author Thomas Bruederli <roundcube@gmail.com>
* @version 1.36
* @link http://ilohamail.org
*/
class rcube_imap
{
var $db;
var $conn;
var $root_ns = '';
var $root_dir = '';
var $mailbox = 'INBOX';
var $list_page = 1;
var $page_size = 10;
var $sort_field = 'date';
var $sort_order = 'DESC';
var $delimiter = NULL;
var $caching_enabled = FALSE;
var $default_folders = array('INBOX');
var $default_folders_lc = array('inbox');
var $cache = array();
var $cache_keys = array();
var $cache_changes = array();
var $uid_id_map = array();
var $msg_headers = array();
var $capabilities = array();
var $skip_deleted = FALSE;
var $search_set = NULL;
var $search_subject = '';
var $search_string = '';
var $search_charset = '';
var $debug_level = 1;
/**
* Object constructor
*
* @param object Database connection
*/
function __construct($db_conn)
{
$this->db = $db_conn;
}
/**
* PHP 4 object constructor
*
* @see rcube_imap::__construct
*/
function rcube_imap($db_conn)
{
$this->__construct($db_conn);
}
/**
* Connect to an IMAP server
*
* @param string Host to connect
* @param string Username for IMAP account
* @param string Password for IMAP account
* @param number Port to connect to
* @param boolean Use SSL connection
* @return boolean TRUE on success, FALSE on failure
* @access public
*/
function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
{
global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
// check for Open-SSL support in PHP build
if ($use_ssl && in_array('openssl', get_loaded_extensions()))
$ICL_SSL = TRUE;
else if ($use_ssl)
{
raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
'message' => 'Open SSL not available;'), TRUE, FALSE);
$port = 143;
}
$ICL_PORT = $port;
$IMAP_USE_INTERNAL_DATE = false;
$this->conn = iil_Connect($host, $user, $pass, array('imap' => 'check'));
$this->host = $host;
$this->user = $user;
$this->pass = $pass;
$this->port = $port;
$this->ssl = $use_ssl;
// print trace mesages
if ($this->conn && ($this->debug_level & 8))
console($this->conn->message);
// write error log
else if (!$this->conn && $GLOBALS['iil_error'])
{
raise_error(array('code' => 403,
'type' => 'imap',
'message' => $GLOBALS['iil_error']), TRUE, FALSE);
}
// get server properties
if ($this->conn)
{
$this->_parse_capability($this->conn->capability);
if (!empty($this->conn->delimiter))
$this->delimiter = $this->conn->delimiter;
if (!empty($this->conn->rootdir))
{
$this->set_rootdir($this->conn->rootdir);
$this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
}
}
return $this->conn ? TRUE : FALSE;
}
/**
* Close IMAP connection
* Usually done on script shutdown
*
* @access public
*/
function close()
{
if ($this->conn)
iil_Close($this->conn);
}
/**
* Close IMAP connection and re-connect
* This is used to avoid some strange socket errors when talking to Courier IMAP
*
* @access public
*/
function reconnect()
{
$this->close();
$this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
}
/**
* Set a root folder for the IMAP connection.
*
* Only folders within this root folder will be displayed
* and all folder paths will be translated using this folder name
*
* @param string Root folder
* @access public
*/
function set_rootdir($root)
{
if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
$root = substr($root, 0, -1);
$this->root_dir = $root;
if (empty($this->delimiter))
$this->get_hierarchy_delimiter();
}
/**
* This list of folders will be listed above all other folders
*
* @param array Indexed list of folder names
* @access public
*/
function set_default_mailboxes($arr)
{
if (is_array($arr))
{
$this->default_folders = $arr;
$this->default_folders_lc = array();
// add inbox if not included
if (!in_array_nocase('INBOX', $this->default_folders))
array_unshift($this->default_folders, 'INBOX');
// create a second list with lower cased names
foreach ($this->default_folders as $mbox)
$this->default_folders_lc[] = strtolower($mbox);
}
}
/**
* Set internal mailbox reference.
*
* All operations will be perfomed on this mailbox/folder
*
* @param string Mailbox/Folder name
* @access public
*/
function set_mailbox($new_mbox)
{
$mailbox = $this->_mod_mailbox($new_mbox);
if ($this->mailbox == $mailbox)
return;
$this->mailbox = $mailbox;
// clear messagecount cache for this mailbox
$this->_clear_messagecount($mailbox);
}
/**
* Set internal list page
*
* @param number Page number to list
* @access public
*/
function set_page($page)
{
$this->list_page = (int)$page;
}
/**
* Set internal page size
*
* @param number Number of messages to display on one page
* @access public
*/
function set_pagesize($size)
{
$this->page_size = (int)$size;
}
/**
* Save a set of message ids for future message listing methods
*
* @param array List of IMAP fields to search in
* @param string Search string
* @param array List of message ids or NULL if empty
*/
function set_search_set($subject, $str=null, $msgs=null, $charset=null)
{
if (is_array($subject) && $str == null && $msgs == null)
list($subject, $str, $msgs, $charset) = $subject;
if ($msgs != null && !is_array($msgs))
$msgs = split(',', $msgs);
$this->search_subject = $subject;
$this->search_string = $str;
$this->search_set = is_array($msgs) ? $msgs : NULL;
$this->search_charset = $charset;
}
/**
* Return the saved search set as hash array
*/
function get_search_set()
{
return array($this->search_subject, $this->search_string, $this->search_set, $this->search_charset);
}
/**
* Returns the currently used mailbox name
*
* @return string Name of the mailbox/folder
* @access public
*/
function get_mailbox_name()
{
return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
}
/**
* Returns the IMAP server's capability
*
* @param string Capability name
* @return mixed Capability value or TRUE if supported, FALSE if not
* @access public
*/
function get_capability($cap)
{
$cap = strtoupper($cap);
return $this->capabilities[$cap];
}
/**
* Returns the delimiter that is used by the IMAP server for folder separation
*
* @return string Delimiter string
* @access public
*/
function get_hierarchy_delimiter()
{
if ($this->conn && empty($this->delimiter))
$this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
if (empty($this->delimiter))
$this->delimiter = '/';
return $this->delimiter;
}
/**
* Public method for mailbox listing.
*
* Converts mailbox name with root dir first
*
* @param string Optional root folder
* @param string Optional filter for mailbox listing
* @return array List of mailboxes/folders
* @access public
*/
function list_mailboxes($root='', $filter='*')
{
$a_out = array();
$a_mboxes = $this->_list_mailboxes($root, $filter);
foreach ($a_mboxes as $mbox_row)
{
$name = $this->_mod_mailbox($mbox_row, 'out');
if (strlen($name))
$a_out[] = $name;
}
// INBOX should always be available
if (!in_array_nocase('INBOX', $a_out))
array_unshift($a_out, 'INBOX');
// sort mailboxes
$a_out = $this->_sort_mailbox_list($a_out);
return $a_out;
}
/**
* Private method for mailbox listing
*
* @return array List of mailboxes/folders
* @access private
* @see rcube_imap::list_mailboxes
*/
function _list_mailboxes($root='', $filter='*')
{
$a_defaults = $a_out = array();
// get cached folder list
$a_mboxes = $this->get_cache('mailboxes');
if (is_array($a_mboxes))
return $a_mboxes;
// retrieve list of folders from IMAP server
$a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
if (!is_array($a_folders) || !sizeof($a_folders))
$a_folders = array();
// write mailboxlist to cache
$this->update_cache('mailboxes', $a_folders);
return $a_folders;
}
/**
* Get message count for a specific mailbox
*
* @param string Mailbox/folder name
* @param string Mode for count [ALL|UNSEEN|RECENT]
* @param boolean Force reading from server and update cache
* @return number Number of messages
* @access public
*/
function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_messagecount($mailbox, $mode, $force);
}
/**
* Private method for getting nr of messages
*
* @access private
* @see rcube_imap::messagecount
*/
function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
{
$a_mailbox_cache = FALSE;
$mode = strtoupper($mode);
if (empty($mailbox))
$mailbox = $this->mailbox;
// count search set
if ($this->search_set && $mailbox == $this->mailbox && $mode == 'ALL')
return count($this->search_set);
$a_mailbox_cache = $this->get_cache('messagecount');
// return cached value
if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
return $a_mailbox_cache[$mailbox][$mode];
// RECENT count is fetched abit different
if ($mode == 'RECENT')
$count = iil_C_CheckForRecent($this->conn, $mailbox);
// use SEARCH for message counting
else if ($this->skip_deleted)
{
$search_str = "ALL UNDELETED";
// get message count and store in cache
if ($mode == 'UNSEEN')
$search_str .= " UNSEEN";
// get message count using SEARCH
// not very performant but more precise (using UNDELETED)
$count = 0;
$index = $this->_search_index($mailbox, $search_str);
if (is_array($index))
{
$str = implode(",", $index);
if (!empty($str))
$count = count($index);
}
}
else
{
if ($mode == 'UNSEEN')
$count = iil_C_CountUnseen($this->conn, $mailbox);
else
$count = iil_C_CountMessages($this->conn, $mailbox);
}
if (!is_array($a_mailbox_cache[$mailbox]))
$a_mailbox_cache[$mailbox] = array();
$a_mailbox_cache[$mailbox][$mode] = (int)$count;
// write back to cache
$this->update_cache('messagecount', $a_mailbox_cache);
return (int)$count;
}
/**
* Public method for listing headers
* convert mailbox name with root dir first
*
* @param string Mailbox/folder name
* @param number Current page to list
* @param string Header field to sort by
* @param string Sort order [ASC|DESC]
* @return array Indexed array with message header objects
* @access public
*/
function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
}
/**
* Private method for listing message headers
*
* @access private
* @see rcube_imap::list_headers
*/
function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
{
if (!strlen($mailbox))
return array();
// use saved message set
if ($this->search_set && $mailbox == $this->mailbox)
return $this->_list_header_set($mailbox, $this->search_set, $page, $sort_field, $sort_order);
if ($sort_field!=NULL)
$this->sort_field = $sort_field;
if ($sort_order!=NULL)
$this->sort_order = strtoupper($sort_order);
$max = $this->_messagecount($mailbox);
$start_msg = ($this->list_page-1) * $this->page_size;
list($begin, $end) = $this->_get_message_range($max, $page);
// mailbox is empty
if ($begin >= $end)
return array();
$headers_sorted = FALSE;
$cache_key = $mailbox.'.msg';
$cache_status = $this->check_cache_status($mailbox, $cache_key);
// cache is OK, we can get all messages from local cache
if ($cache_status>0)
{
$a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
$headers_sorted = TRUE;
}
// cache is dirty, sync it
else if ($this->caching_enabled && $cache_status==-1 && !$recursive)
{
$this->sync_header_index($mailbox);
return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
}
else
{
// retrieve headers from IMAP
if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
{
$msgs = $msg_index[$begin];
for ($i=$begin+1; $i < $end; $i++)
$msgs = $msgs.','.$msg_index[$i];
}
else
{
$msgs = sprintf("%d:%d", $begin+1, $end);
$i = 0;
for ($msg_seqnum = $begin; $msg_seqnum <= $end; $msg_seqnum++)
$msg_index[$i++] = $msg_seqnum;
}
// use this class for message sorting
$sorter = new rcube_header_sorter();
$sorter->set_sequence_numbers($msg_index);
// fetch reuested headers from server
$a_msg_headers = array();
$deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
// delete cached messages with a higher index than $max
$this->clear_message_cache($cache_key, $max);
// kick child process to sync cache
// ...
}
// return empty array if no messages found
if (!is_array($a_msg_headers) || empty($a_msg_headers))
return array();
// if not already sorted
if (!$headers_sorted)
{
$sorter->sort_headers($a_msg_headers);
if ($this->sort_order == 'DESC')
$a_msg_headers = array_reverse($a_msg_headers);
}
return array_values($a_msg_headers);
}
/**
* Public method for listing a specific set of headers
* convert mailbox name with root dir first
*
* @param string Mailbox/folder name
* @param array List of message ids to list
* @param number Current page to list
* @param string Header field to sort by
* @param string Sort order [ASC|DESC]
* @return array Indexed array with message header objects
* @access public
*/
function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);
}
/**
* Private method for listing a set of message headers
*
* @access private
* @see rcube_imap::list_header_set
*/
function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
{
// also accept a comma-separated list of message ids
if (is_string($msgs))
$msgs = split(',', $msgs);
if (!strlen($mailbox) || empty($msgs))
return array();
if ($sort_field!=NULL)
$this->sort_field = $sort_field;
if ($sort_order!=NULL)
$this->sort_order = strtoupper($sort_order);
$max = count($msgs);
$start_msg = ($this->list_page-1) * $this->page_size;
// fetch reuested headers from server
$a_msg_headers = array();
$this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
// return empty array if no messages found
if (!is_array($a_msg_headers) || empty($a_msg_headers))
return array();
// if not already sorted
$a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
// only return the requested part of the set
return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
}
/**
* Helper function to get first and last index of the requested set
*
* @param number message count
* @param mixed page number to show, or string 'all'
* @return array array with two values: first index, last index
* @access private
*/
function _get_message_range($max, $page)
{
$start_msg = ($this->list_page-1) * $this->page_size;
if ($page=='all')
{
$begin = 0;
$end = $max;
}
else if ($this->sort_order=='DESC')
{
$begin = $max - $this->page_size - $start_msg;
$end = $max - $start_msg;
}
else
{
$begin = $start_msg;
$end = $start_msg + $this->page_size;
}
if ($begin < 0) $begin = 0;
if ($end < 0) $end = $max;
if ($end > $max) $end = $max;
return array($begin, $end);
}
/**
* Fetches message headers
* Used for loop
*
* @param string Mailbox name
* @param string Message index to fetch
* @param array Reference to message headers array
* @param array Array with cache index
* @return number Number of deleted messages
* @access private
*/
function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
{
// cache is incomplete
$cache_index = $this->get_message_cache_index($cache_key);
// fetch reuested headers from server
$a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
$deleted_count = 0;
if (!empty($a_header_index))
{
foreach ($a_header_index as $i => $headers)
{
if ($headers->deleted && $this->skip_deleted)
{
// delete from cache
if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
$this->remove_message_cache($cache_key, $headers->id);
$deleted_count++;
continue;
}
// add message to cache
if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
$this->add_message_cache($cache_key, $headers->id, $headers);
$a_msg_headers[$headers->uid] = $headers;
}
}
return $deleted_count;
}
/**
* Return sorted array of message UIDs
*
* @param string Mailbox to get index from
* @param string Sort column
* @param string Sort order [ASC, DESC]
* @return array Indexed array with message ids
*/
function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
{
if ($sort_field!=NULL)
$this->sort_field = $sort_field;
if ($sort_order!=NULL)
$this->sort_order = strtoupper($sort_order);
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
$key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
// have stored it in RAM
if (isset($this->cache[$key]))
return $this->cache[$key];
// check local cache
$cache_key = $mailbox.'.msg';
$cache_status = $this->check_cache_status($mailbox, $cache_key);
// cache is OK
if ($cache_status>0)
{
$a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
return array_values($a_index);
}
// fetch complete message index
$msg_count = $this->_messagecount($mailbox);
if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
{
if ($this->sort_order == 'DESC')
$a_index = array_reverse($a_index);
$this->cache[$key] = $a_index;
}
else
{
$a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
$a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
if ($this->sort_order=="ASC")
asort($a_index);
else if ($this->sort_order=="DESC")
arsort($a_index);
$i = 0;
$this->cache[$key] = array();
foreach ($a_index as $index => $value)
$this->cache[$key][$i++] = $a_uids[$index];
}
return $this->cache[$key];
}
function sync_header_index($mailbox)
{
$cache_key = $mailbox.'.msg';
$cache_index = $this->get_message_cache_index($cache_key);
$msg_count = $this->_messagecount($mailbox);
// fetch complete message index
$a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
foreach ($a_message_index as $id => $uid)
{
// message in cache at correct position
if ($cache_index[$id] == $uid)
{
unset($cache_index[$id]);
continue;
}
// message in cache but in wrong position
if (in_array((string)$uid, $cache_index, TRUE))
{
unset($cache_index[$id]);
}
// other message at this position
if (isset($cache_index[$id]))
{
$this->remove_message_cache($cache_key, $id);
unset($cache_index[$id]);
}
// fetch complete headers and add to cache
$headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
$this->add_message_cache($cache_key, $headers->id, $headers);
}
// those ids that are still in cache_index have been deleted
if (!empty($cache_index))
{
foreach ($cache_index as $id => $uid)
$this->remove_message_cache($cache_key, $id);
}
}
/**
* Invoke search request to IMAP server
*
* @param string mailbox name to search in
* @param string search criteria (ALL, TO, FROM, SUBJECT, etc)
* @param string search string
* @return array search results as list of message ids
* @access public
*/
function search($mbox_name='', $criteria='ALL', $str=NULL, $charset=NULL)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
// have an array of criterias => execute multiple searches
if (is_array($criteria) && $str)
{
$results = array();
foreach ($criteria as $crit)
$results = array_merge($results, $this->search($mbox_name, $crit, $str, $charset));
$results = array_unique($results);
$this->set_search_set($criteria, $str, $results, $charset);
return $results;
}
else if ($str && $criteria)
{
$search = (!empty($charset) ? "CHARSET $charset " : '') . sprintf("%s {%d}\r\n%s", $criteria, strlen($str), $str);
$results = $this->_search_index($mailbox, $search);
// try search with ISO charset (should be supported by server)
if (empty($results) && !empty($charset) && $charset!='ISO-8859-1')
$results = $this->search($mbox_name, $criteria, rcube_charset_convert($str, $charset, 'ISO-8859-1'), 'ISO-8859-1');
$this->set_search_set($criteria, $str, $results, $charset);
return $results;
}
else
return $this->_search_index($mailbox, $criteria);
}
/**
* Private search method
*
* @return array search results as list of message ids
* @access private
* @see rcube_imap::search()
*/
function _search_index($mailbox, $criteria='ALL')
{
$a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
// clean message list (there might be some empty entries)
if (is_array($a_messages))
{
foreach ($a_messages as $i => $val)
if (empty($val))
unset($a_messages[$i]);
}
return $a_messages;
}
/**
* Refresh saved search set
*/
function refresh_search()
{
if (!empty($this->search_subject) && !empty($this->search_string))
$this->search_set = $this->search('', $this->search_subject, $this->search_string, $this->search_charset);
return $this->get_search_set();
}
/**
* Return message headers object of a specific message
*
* @param int Message ID
* @param string Mailbox to read from
* @param boolean True if $id is the message UID
* @return object Message headers representation
*/
function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
$uid = $is_uid ? $id : $this->_id2uid($id);
// get cached headers
if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid)))
return $headers;
$headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid);
// write headers cache
if ($headers)
{
if ($is_uid)
$this->uid_id_map[$mbox_name][$uid] = $headers->id;
$this->add_message_cache($mailbox.'.msg', $headers->id, $headers);
}
return $headers;
}
/**
* Fetch body structure from the IMAP server and build
* an object structure similar to the one generated by PEAR::Mail_mimeDecode
*
* @param Int Message UID to fetch
* @return object Standard object tree or False on failure
*/
function &get_structure($uid)
{
$cache_key = $this->mailbox.'.msg';
$headers = &$this->get_cached_message($cache_key, $uid, true);
// return cached message structure
if (is_object($headers) && is_object($headers->structure))
return $headers->structure;
// resolve message sequence number
if (!($msg_id = $this->_uid2id($uid)))
return FALSE;
$structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
$structure = iml_GetRawStructureArray($structure_str);
$struct = false;
// parse structure and add headers
if (!empty($structure))
{
$this->_msg_id = $msg_id;
$headers = $this->get_headers($msg_id, NULL, FALSE);
$struct = &$this->_structure_part($structure);
$struct->headers = get_object_vars($headers);
// don't trust given content-type
if (empty($struct->parts) && !empty($struct->headers['ctype']))
{
$struct->mime_id = '1';
$struct->mimetype = strtolower($struct->headers['ctype']);
list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
}
// write structure to cache
if ($this->caching_enabled)
$this->add_message_cache($cache_key, $msg_id, $headers, $struct);
}
return $struct;
}
/**
* Build message part object
*
* @access private
*/
function &_structure_part($part, $count=0, $parent='')
{
$struct = new rcube_message_part;
$struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
// multipart
if (is_array($part[0]))
{
$struct->ctype_primary = 'multipart';
// find first non-array entry
for ($i=1; count($part); $i++)
if (!is_array($part[$i]))
{
$struct->ctype_secondary = strtolower($part[$i]);
break;
}
$struct->mimetype = 'multipart/'.$struct->ctype_secondary;
$struct->parts = array();
for ($i=0, $count=0; $i<count($part); $i++)
if (is_array($part[$i]) && count($part[$i]) > 5)
$struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id);
return $struct;
}
// regular part
$struct->ctype_primary = strtolower($part[0]);
$struct->ctype_secondary = strtolower($part[1]);
$struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary;
// read content type parameters
if (is_array($part[2]))
{
$struct->ctype_parameters = array();
for ($i=0; $i<count($part[2]); $i+=2)
$struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
if (isset($struct->ctype_parameters['charset']))
$struct->charset = $struct->ctype_parameters['charset'];
}
// read content encoding
if (!empty($part[5]) && $part[5]!='NIL')
{
$struct->encoding = strtolower($part[5]);
$struct->headers['content-transfer-encoding'] = $struct->encoding;
}
// get part size
if (!empty($part[6]) && $part[6]!='NIL')
$struct->size = intval($part[6]);
// read part disposition
$di = count($part) - 2;
if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) ||
(is_array($part[--$di]) && count($part[$di]) == 2))
{
$struct->disposition = strtolower($part[$di][0]);
if (is_array($part[$di][1]))
for ($n=0; $n<count($part[$di][1]); $n+=2)
$struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
}
// get child parts
if (is_array($part[8]) && $di != 8)
{
$struct->parts = array();
for ($i=0, $count=0; $i<count($part[8]); $i++)
if (is_array($part[8][$i]) && count($part[8][$i]) > 5)
$struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
}
// get part ID
if (!empty($part[3]) && $part[3]!='NIL')
{
$struct->content_id = $part[3];
$struct->headers['content-id'] = $part[3];
if (empty($struct->disposition))
$struct->disposition = 'inline';
}
// fetch message headers if message/rfc822
if ($struct->ctype_primary=='message')
{
$headers = iil_C_FetchPartBody($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id.'.HEADER');
$struct->headers = $this->_parse_headers($headers);
}
return $struct;
}
/**
- * Return a flat array with references to all parts, indexed by part numbmers
+ * Return a flat array with references to all parts, indexed by part numbers
*
* @param object Message body structure
* @return Array with part number -> object pairs
*/
function get_mime_numbers(&$structure)
{
$a_parts = array();
$this->_get_part_numbers($structure, $a_parts);
return $a_parts;
}
/**
* Helper method for recursive calls
*
* @access
*/
function _get_part_numbers(&$part, &$a_parts)
{
if ($part->mime_id)
$a_parts[$part->mime_id] = &$part;
if (is_array($part->parts))
for ($i=0; $i<count($part->parts); $i++)
$this->_get_part_numbers($part->parts[$i], $a_parts);
}
/**
* Fetch message body of a specific message from the server
*
* @param int Message UID
* @param string Part number
* @param object Part object created by get_structure()
* @param mixed True to print part, ressource to write part contents in
* @return Message/part body if not printed
*/
function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL)
{
if (!($msg_id = $this->_uid2id($uid)))
return FALSE;
// get part encoding if not provided
if (!is_object($o_part))
{
$structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
$structure = iml_GetRawStructureArray($structure_str);
$part_type = iml_GetPartTypeCode($structure, $part);
$o_part = new rcube_message_part;
$o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other');
$o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part));
$o_part->charset = iml_GetPartCharset($structure, $part);
}
// TODO: Add caching for message parts
if ($print)
{
iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, ($o_part->encoding=='base64'?3:2));
$body = TRUE;
}
else
{
$body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 1);
// decode part body
if ($o_part->encoding=='base64' || $o_part->encoding=='quoted-printable')
$body = $this->mime_decode($body, $o_part->encoding);
// convert charset (if text or message part)
if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message')
{
// assume ISO-8859-1 if no charset specified
if (empty($o_part->charset))
$o_part->charset = 'ISO-8859-1';
$body = rcube_charset_convert($body, $o_part->charset);
}
}
return $body;
}
/**
* Fetch message body of a specific message from the server
*
* @param int Message UID
* @return Message/part body
* @see ::get_message_part()
*/
function &get_body($uid, $part=1)
{
return $this->get_message_part($uid, $part);
}
/**
* Returns the whole message source as string
*
* @param int Message UID
* @return Message source string
*/
function &get_raw_body($uid)
{
if (!($msg_id = $this->_uid2id($uid)))
return FALSE;
$body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
$body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
return $body;
}
/**
* Sends the whole message source to stdout
*
* @param int Message UID
*/
function print_raw_body($uid)
{
if (!($msg_id = $this->_uid2id($uid)))
return FALSE;
print iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
flush();
iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2);
}
/**
* Set message flag to one or several messages
*
* @param mixed Message UIDs as array or as comma-separated string
* @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT
* @return True on success, False on failure
*/
function set_flag($uids, $flag)
{
$flag = strtoupper($flag);
$msg_ids = array();
if (!is_array($uids))
$uids = explode(',',$uids);
foreach ($uids as $uid) {
$msg_ids[$uid] = $this->_uid2id($uid);
}
if ($flag=='UNDELETED')
$result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
else if ($flag=='UNSEEN')
$result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
else
$result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
// reload message headers if cached
$cache_key = $this->mailbox.'.msg';
if ($this->caching_enabled)
{
foreach ($msg_ids as $uid => $id)
{
if ($cached_headers = $this->get_cached_message($cache_key, $uid))
{
$this->remove_message_cache($cache_key, $id);
//$this->get_headers($uid);
}
}
// close and re-open connection
// this prevents connection problems with Courier
$this->reconnect();
}
// set nr of messages that were flaged
$count = count($msg_ids);
// clear message count cache
if ($result && $flag=='SEEN')
$this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
else if ($result && $flag=='UNSEEN')
$this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
else if ($result && $flag=='DELETED')
$this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
return $result;
}
// append a mail message (source) to a specific mailbox
function save_message($mbox_name, &$message)
{
$mbox_name = stripslashes($mbox_name);
$mailbox = $this->_mod_mailbox($mbox_name);
// make sure mailbox exists
if (in_array($mailbox, $this->_list_mailboxes()))
$saved = iil_C_Append($this->conn, $mailbox, $message);
if ($saved)
{
// increase messagecount of the target mailbox
$this->_set_messagecount($mailbox, 'ALL', 1);
}
return $saved;
}
// move a message from one mailbox to another
function move_message($uids, $to_mbox, $from_mbox='')
{
$to_mbox = stripslashes($to_mbox);
$from_mbox = stripslashes($from_mbox);
$to_mbox = $this->_mod_mailbox($to_mbox);
$from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
// make sure mailbox exists
if (!in_array($to_mbox, $this->_list_mailboxes()))
{
if (in_array(strtolower($to_mbox), $this->default_folders))
$this->create_mailbox($to_mbox, TRUE);
else
return FALSE;
}
// convert the list of uids to array
$a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
// exit if no message uids are specified
if (!is_array($a_uids))
return false;
// convert uids to message ids
$a_mids = array();
foreach ($a_uids as $uid)
$a_mids[] = $this->_uid2id($uid, $from_mbox);
$moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
// send expunge command in order to have the moved message
// really deleted from the source mailbox
if ($moved)
{
$this->_expunge($from_mbox, FALSE);
$this->_clear_messagecount($from_mbox);
$this->_clear_messagecount($to_mbox);
}
// remove message ids from search set
if ($moved && $this->search_set && $from_mbox == $this->mailbox)
$this->search_set = array_diff($this->search_set, $a_mids);
// update cached message headers
$cache_key = $from_mbox.'.msg';
if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
{
$start_index = 100000;
foreach ($a_uids as $uid)
{
if (($index = array_search($uid, $a_cache_index)) !== FALSE)
$start_index = min($index, $start_index);
}
// clear cache from the lowest index on
$this->clear_message_cache($cache_key, $start_index);
}
return $moved;
}
// mark messages as deleted and expunge mailbox
function delete_message($uids, $mbox_name='')
{
$mbox_name = stripslashes($mbox_name);
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
// convert the list of uids to array
$a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
// exit if no message uids are specified
if (!is_array($a_uids))
return false;
// convert uids to message ids
$a_mids = array();
foreach ($a_uids as $uid)
$a_mids[] = $this->_uid2id($uid, $mailbox);
$deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
// send expunge command in order to have the deleted message
// really deleted from the mailbox
if ($deleted)
{
$this->_expunge($mailbox, FALSE);
$this->_clear_messagecount($mailbox);
}
// remove message ids from search set
if ($moved && $this->search_set && $mailbox == $this->mailbox)
$this->search_set = array_diff($this->search_set, $a_mids);
// remove deleted messages from cache
$cache_key = $mailbox.'.msg';
if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
{
$start_index = 100000;
foreach ($a_uids as $uid)
{
$index = array_search($uid, $a_cache_index);
$start_index = min($index, $start_index);
}
// clear cache from the lowest index on
$this->clear_message_cache($cache_key, $start_index);
}
return $deleted;
}
// clear all messages in a specific mailbox
function clear_mailbox($mbox_name=NULL)
{
$mbox_name = stripslashes($mbox_name);
$mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
$msg_count = $this->_messagecount($mailbox, 'ALL');
if ($msg_count>0)
{
$cleared = iil_C_ClearFolder($this->conn, $mailbox);
// make sure the message count cache is cleared as well
if ($cleared)
{
$this->clear_message_cache($mailbox.'.msg');
$a_mailbox_cache = $this->get_cache('messagecount');
unset($a_mailbox_cache[$mailbox]);
$this->update_cache('messagecount', $a_mailbox_cache);
}
return $cleared;
}
else
return 0;
}
// send IMAP expunge command and clear cache
function expunge($mbox_name='', $clear_cache=TRUE)
{
$mbox_name = stripslashes($mbox_name);
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_expunge($mailbox, $clear_cache);
}
// send IMAP expunge command and clear cache
function _expunge($mailbox, $clear_cache=TRUE)
{
$result = iil_C_Expunge($this->conn, $mailbox);
if ($result>=0 && $clear_cache)
{
//$this->clear_message_cache($mailbox.'.msg');
$this->_clear_messagecount($mailbox);
}
return $result;
}
/* --------------------------------
* folder managment
* --------------------------------*/
/**
* Get a list of all folders available on the IMAP server
*
* @param string IMAP root dir
* @return array Inbdexed array with folder names
*/
function list_unsubscribed($root='')
{
static $sa_unsubscribed;
if (is_array($sa_unsubscribed))
return $sa_unsubscribed;
// retrieve list of folders from IMAP server
$a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
// modify names with root dir
foreach ($a_mboxes as $mbox_name)
{
$name = $this->_mod_mailbox($mbox_name, 'out');
if (strlen($name))
$a_folders[] = $name;
}
// filter folders and sort them
$sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
return $sa_unsubscribed;
}
/**
* Get quota
* added by Nuny
*/
function get_quota()
{
if ($this->get_capability('QUOTA'))
return iil_C_GetQuota($this->conn);
return FALSE;
}
/**
* subscribe to a specific mailbox(es)
*/
function subscribe($mbox_name, $mode='subscribe')
{
if (is_array($mbox_name))
$a_mboxes = $mbox_name;
else if (is_string($mbox_name) && strlen($mbox_name))
$a_mboxes = explode(',', $mbox_name);
// let this common function do the main work
return $this->_change_subscription($a_mboxes, 'subscribe');
}
/**
* unsubscribe mailboxes
*/
function unsubscribe($mbox_name)
{
if (is_array($mbox_name))
$a_mboxes = $mbox_name;
else if (is_string($mbox_name) && strlen($mbox_name))
$a_mboxes = explode(',', $mbox_name);
// let this common function do the main work
return $this->_change_subscription($a_mboxes, 'unsubscribe');
}
/**
* Create a new mailbox on the server and register it in local cache
*
* @param string New mailbox name (as utf-7 string)
* @param boolean True if the new mailbox should be subscribed
* @param string Name of the created mailbox, false on error
*/
function create_mailbox($name, $subscribe=FALSE)
{
$result = FALSE;
// replace backslashes
$name = preg_replace('/[\\\]+/', '-', $name);
// reduce mailbox name to 100 chars
$name = substr($name, 0, 100);
$abs_name = $this->_mod_mailbox($name);
$a_mailbox_cache = $this->get_cache('mailboxes');
if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array_nocase($abs_name, $a_mailbox_cache)))
$result = iil_C_CreateFolder($this->conn, $abs_name);
// try to subscribe it
if ($subscribe)
$this->subscribe($name);
return $result ? $name : FALSE;
}
/**
* Set a new name to an existing mailbox
*
* @param string Mailbox to rename (as utf-7 string)
* @param string New mailbox name (as utf-7 string)
* @param string Name of the renames mailbox, false on error
*/
function rename_mailbox($mbox_name, $new_name)
{
$result = FALSE;
// replace backslashes
$name = preg_replace('/[\\\]+/', '-', $new_name);
// encode mailbox name and reduce it to 100 chars
$name = substr($new_name, 0, 100);
// make absolute path
$mailbox = $this->_mod_mailbox($mbox_name);
$abs_name = $this->_mod_mailbox($name);
// check if mailbox is subscribed
$a_subscribed = $this->_list_mailboxes();
$subscribed = in_array($mailbox, $a_subscribed);
// unsubscribe folder
if ($subscribed)
iil_C_UnSubscribe($this->conn, $mailbox);
if (strlen($abs_name))
$result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
// clear cache
if ($result)
{
$this->clear_message_cache($mailbox.'.msg');
$this->clear_cache('mailboxes');
}
// try to subscribe it
if ($result && $subscribed)
iil_C_Subscribe($this->conn, $abs_name);
return $result ? $name : FALSE;
}
/**
* remove mailboxes from server
*/
function delete_mailbox($mbox_name)
{
$deleted = FALSE;
if (is_array($mbox_name))
$a_mboxes = $mbox_name;
else if (is_string($mbox_name) && strlen($mbox_name))
$a_mboxes = explode(',', $mbox_name);
if (is_array($a_mboxes))
foreach ($a_mboxes as $mbox_name)
{
$mailbox = $this->_mod_mailbox($mbox_name);
// unsubscribe mailbox before deleting
iil_C_UnSubscribe($this->conn, $mailbox);
// send delete command to server
$result = iil_C_DeleteFolder($this->conn, $mailbox);
if ($result>=0)
$deleted = TRUE;
}
// clear mailboxlist cache
if ($deleted)
{
$this->clear_message_cache($mailbox.'.msg');
$this->clear_cache('mailboxes');
}
return $deleted;
}
/**
* Create all folders specified as default
*/
function create_default_folders()
{
$a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*');
$a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*');
// create default folders if they do not exist
foreach ($this->default_folders as $folder)
{
$abs_name = $this->_mod_mailbox($folder);
if (!in_array_nocase($abs_name, $a_subscribed))
{
if (!in_array_nocase($abs_name, $a_folders))
$this->create_mailbox($folder, TRUE);
else
$this->subscribe($folder);
}
}
}
/* --------------------------------
* internal caching methods
* --------------------------------*/
function set_caching($set)
{
if ($set && is_object($this->db))
$this->caching_enabled = TRUE;
else
$this->caching_enabled = FALSE;
}
function get_cache($key)
{
// read cache
if (!isset($this->cache[$key]) && $this->caching_enabled)
{
$cache_data = $this->_read_cache_record('IMAP.'.$key);
$this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
}
return $this->cache[$key];
}
function update_cache($key, $data)
{
$this->cache[$key] = $data;
$this->cache_changed = TRUE;
$this->cache_changes[$key] = TRUE;
}
function write_cache()
{
if ($this->caching_enabled && $this->cache_changed)
{
foreach ($this->cache as $key => $data)
{
if ($this->cache_changes[$key])
$this->_write_cache_record('IMAP.'.$key, serialize($data));
}
}
}
function clear_cache($key=NULL)
{
if ($key===NULL)
{
foreach ($this->cache as $key => $data)
$this->_clear_cache_record('IMAP.'.$key);
$this->cache = array();
$this->cache_changed = FALSE;
$this->cache_changes = array();
}
else
{
$this->_clear_cache_record('IMAP.'.$key);
$this->cache_changes[$key] = FALSE;
unset($this->cache[$key]);
}
}
function _read_cache_record($key)
{
$cache_data = FALSE;
if ($this->db)
{
// get cached data from DB
$sql_result = $this->db->query(
"SELECT cache_id, data
FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key=?",
$_SESSION['user_id'],
$key);
if ($sql_arr = $this->db->fetch_assoc($sql_result))
{
$cache_data = $sql_arr['data'];
$this->cache_keys[$key] = $sql_arr['cache_id'];
}
}
return $cache_data;
}
function _write_cache_record($key, $data)
{
if (!$this->db)
return FALSE;
// check if we already have a cache entry for this key
if (!isset($this->cache_keys[$key]))
{
$sql_result = $this->db->query(
"SELECT cache_id
FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key=?",
$_SESSION['user_id'],
$key);
if ($sql_arr = $this->db->fetch_assoc($sql_result))
$this->cache_keys[$key] = $sql_arr['cache_id'];
else
$this->cache_keys[$key] = FALSE;
}
// update existing cache record
if ($this->cache_keys[$key])
{
$this->db->query(
"UPDATE ".get_table_name('cache')."
SET created=".$this->db->now().",
data=?
WHERE user_id=?
AND cache_key=?",
$data,
$_SESSION['user_id'],
$key);
}
// add new cache record
else
{
$this->db->query(
"INSERT INTO ".get_table_name('cache')."
(created, user_id, cache_key, data)
VALUES (".$this->db->now().", ?, ?, ?)",
$_SESSION['user_id'],
$key,
$data);
}
}
function _clear_cache_record($key)
{
$this->db->query(
"DELETE FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key=?",
$_SESSION['user_id'],
$key);
}
/* --------------------------------
* message caching methods
* --------------------------------*/
// checks if the cache is up-to-date
// return: -3 = off, -2 = incomplete, -1 = dirty
function check_cache_status($mailbox, $cache_key)
{
if (!$this->caching_enabled)
return -3;
$cache_index = $this->get_message_cache_index($cache_key, TRUE);
$msg_count = $this->_messagecount($mailbox);
$cache_count = count($cache_index);
// console("Cache check: $msg_count !== ".count($cache_index));
if ($cache_count==$msg_count)
{
// get highest index
$header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
$cache_uid = array_pop($cache_index);
// uids of highest message matches -> cache seems OK
if ($cache_uid == $header->uid)
return 1;
// cache is dirty
return -1;
}
// if cache count differs less than 10% report as dirty
else if (abs($msg_count - $cache_count) < $msg_count/10)
return -1;
else
return -2;
}
function get_message_cache($key, $from, $to, $sort_field, $sort_order)
{
$cache_key = "$key:$from:$to:$sort_field:$sort_order";
$db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
if (!in_array($sort_field, $db_header_fields))
$sort_field = 'idx';
if ($this->caching_enabled && !isset($this->cache[$cache_key]))
{
$this->cache[$cache_key] = array();
$sql_result = $this->db->limitquery(
"SELECT idx, uid, headers
FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
strtoupper($sort_order),
$from,
$to-$from,
$_SESSION['user_id'],
$key);
while ($sql_arr = $this->db->fetch_assoc($sql_result))
{
$uid = $sql_arr['uid'];
$this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
}
}
return $this->cache[$cache_key];
}
function &get_cached_message($key, $uid, $struct=false)
{
if (!$this->caching_enabled)
return FALSE;
$internal_key = '__single_msg';
if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) ||
($struct && empty($this->cache[$internal_key][$uid]->structure))))
{
$sql_select = "idx, uid, headers" . ($struct ? ", structure" : '');
$sql_result = $this->db->query(
"SELECT $sql_select
FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
AND uid=?",
$_SESSION['user_id'],
$key,
$uid);
if ($sql_arr = $this->db->fetch_assoc($sql_result))
{
$this->cache[$internal_key][$uid] = unserialize($sql_arr['headers']);
if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure']))
$this->cache[$internal_key][$uid]->structure = unserialize($sql_arr['structure']);
}
}
return $this->cache[$internal_key][$uid];
}
function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
{
static $sa_message_index = array();
// empty key -> empty array
if (empty($key))
return array();
if (!empty($sa_message_index[$key]) && !$force)
return $sa_message_index[$key];
$sa_message_index[$key] = array();
$sql_result = $this->db->query(
"SELECT idx, uid
FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
$_SESSION['user_id'],
$key);
while ($sql_arr = $this->db->fetch_assoc($sql_result))
$sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
return $sa_message_index[$key];
}
function add_message_cache($key, $index, $headers, $struct=null)
{
if (empty($key) || !is_object($headers) || empty($headers->uid))
return;
// check for an existing record (probly headers are cached but structure not)
$sql_result = $this->db->query(
"SELECT message_id
FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
AND uid=?
AND del<>1",
$_SESSION['user_id'],
$key,
$headers->uid);
// update cache record
if ($sql_arr = $this->db->fetch_assoc($sql_result))
{
$this->db->query(
"UPDATE ".get_table_name('messages')."
SET idx=?, headers=?, structure=?
WHERE message_id=?",
$index,
serialize($headers),
is_object($struct) ? serialize($struct) : NULL,
$sql_arr['message_id']
);
}
else // insert new record
{
$this->db->query(
"INSERT INTO ".get_table_name('messages')."
(user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers, structure)
VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?, ?)",
$_SESSION['user_id'],
$key,
$index,
$headers->uid,
(string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
(string)substr($this->decode_header($headers->from, TRUE), 0, 128),
(string)substr($this->decode_header($headers->to, TRUE), 0, 128),
(string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
(int)$headers->size,
serialize($headers),
is_object($struct) ? serialize($struct) : NULL
);
}
}
function remove_message_cache($key, $index)
{
$this->db->query(
"DELETE FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
AND idx=?",
$_SESSION['user_id'],
$key,
$index);
}
function clear_message_cache($key, $start_index=1)
{
$this->db->query(
"DELETE FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
AND idx>=?",
$_SESSION['user_id'],
$key,
$start_index);
}
/* --------------------------------
* encoding/decoding methods
* --------------------------------*/
function decode_address_list($input, $max=NULL)
{
$a = $this->_parse_address_list($input);
$out = array();
if (!is_array($a))
return $out;
$c = count($a);
$j = 0;
foreach ($a as $val)
{
$j++;
$address = $val['address'];
$name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
$string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
$out[$j] = array('name' => $name,
'mailto' => $address,
'string' => $string);
if ($max && $j==$max)
break;
}
return $out;
}
function decode_header($input, $remove_quotes=FALSE)
{
$str = $this->decode_mime_string((string)$input);
if ($str{0}=='"' && $remove_quotes)
{
$str = str_replace('"', '', $str);
}
return $str;
}
/**
* Decode a mime-encoded string to internal charset
*
* @access static
*/
function decode_mime_string($input, $recursive=false)
{
$out = '';
$pos = strpos($input, '=?');
if ($pos !== false)
{
$out = substr($input, 0, $pos);
$end_cs_pos = strpos($input, "?", $pos+2);
$end_en_pos = strpos($input, "?", $end_cs_pos+1);
$end_pos = strpos($input, "?=", $end_en_pos+1);
$encstr = substr($input, $pos+2, ($end_pos-$pos-2));
$rest = substr($input, $end_pos+2);
$out .= rcube_imap::_decode_mime_string_part($encstr);
$out .= rcube_imap::decode_mime_string($rest);
return $out;
}
// no encoding information, defaults to what is specified in the class header
return rcube_charset_convert($input, 'ISO-8859-1');
}
/**
* Decode a part of a mime-encoded string
*
* @access static
*/
function _decode_mime_string_part($str)
{
$a = explode('?', $str);
$count = count($a);
// should be in format "charset?encoding?base64_string"
if ($count >= 3)
{
for ($i=2; $i<$count; $i++)
$rest.=$a[$i];
if (($a[1]=="B")||($a[1]=="b"))
$rest = base64_decode($rest);
else if (($a[1]=="Q")||($a[1]=="q"))
{
$rest = str_replace("_", " ", $rest);
$rest = quoted_printable_decode($rest);
}
return rcube_charset_convert($rest, $a[0]);
}
else
return $str; // we dont' know what to do with this
}
function mime_decode($input, $encoding='7bit')
{
switch (strtolower($encoding))
{
case '7bit':
return $input;
break;
case 'quoted-printable':
return quoted_printable_decode($input);
break;
case 'base64':
return base64_decode($input);
break;
default:
return $input;
}
}
function mime_encode($input, $encoding='7bit')
{
switch ($encoding)
{
case 'quoted-printable':
return quoted_printable_encode($input);
break;
case 'base64':
return base64_encode($input);
break;
default:
return $input;
}
}
// convert body chars according to the ctype_parameters
function charset_decode($body, $ctype_param)
{
if (is_array($ctype_param) && !empty($ctype_param['charset']))
return rcube_charset_convert($body, $ctype_param['charset']);
// defaults to what is specified in the class header
return rcube_charset_convert($body, 'ISO-8859-1');
}
/* --------------------------------
* private methods
* --------------------------------*/
function _mod_mailbox($mbox_name, $mode='in')
{
if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX')
return $mbox_name;
if (!empty($this->root_dir) && $mode=='in')
$mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
else if (strlen($this->root_dir) && $mode=='out')
$mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
return $mbox_name;
}
// sort mailboxes first by default folders and then in alphabethical order
function _sort_mailbox_list($a_folders)
{
$a_out = $a_defaults = array();
// find default folders and skip folders starting with '.'
foreach($a_folders as $i => $folder)
{
if ($folder{0}=='.')
continue;
if (($p = array_search(strtolower($folder), $this->default_folders_lc))!==FALSE)
$a_defaults[$p] = $folder;
else
$a_out[] = $folder;
}
sort($a_out);
ksort($a_defaults);
return array_merge($a_defaults, $a_out);
}
function get_id($uid, $mbox_name=NULL)
{
return $this->_uid2id($uid, $mbox_name);
}
function get_uid($id,$mbox_name=NULL)
{
return $this->_id2uid($id, $mbox_name);
}
function _uid2id($uid, $mbox_name=NULL)
{
if (!$mbox_name)
$mbox_name = $this->mailbox;
if (!isset($this->uid_id_map[$mbox_name][$uid]))
$this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
return $this->uid_id_map[$mbox_name][$uid];
}
function _id2uid($id, $mbox_name=NULL)
{
if (!$mbox_name)
$mbox_name = $this->mailbox;
return iil_C_ID2UID($this->conn, $mbox_name, $id);
}
// parse string or array of server capabilities and put them in internal array
function _parse_capability($caps)
{
if (!is_array($caps))
$cap_arr = explode(' ', $caps);
else
$cap_arr = $caps;
foreach ($cap_arr as $cap)
{
if ($cap=='CAPABILITY')
continue;
if (strpos($cap, '=')>0)
{
list($key, $value) = explode('=', $cap);
if (!is_array($this->capabilities[$key]))
$this->capabilities[$key] = array();
$this->capabilities[$key][] = $value;
}
else
$this->capabilities[$cap] = TRUE;
}
}
// subscribe/unsubscribe a list of mailboxes and update local cache
function _change_subscription($a_mboxes, $mode)
{
$updated = FALSE;
if (is_array($a_mboxes))
foreach ($a_mboxes as $i => $mbox_name)
{
$mailbox = $this->_mod_mailbox($mbox_name);
$a_mboxes[$i] = $mailbox;
if ($mode=='subscribe')
$result = iil_C_Subscribe($this->conn, $mailbox);
else if ($mode=='unsubscribe')
$result = iil_C_UnSubscribe($this->conn, $mailbox);
if ($result>=0)
$updated = TRUE;
}
// get cached mailbox list
if ($updated)
{
$a_mailbox_cache = $this->get_cache('mailboxes');
if (!is_array($a_mailbox_cache))
return $updated;
// modify cached list
if ($mode=='subscribe')
$a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
else if ($mode=='unsubscribe')
$a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
// write mailboxlist to cache
$this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
}
return $updated;
}
// increde/decrese messagecount for a specific mailbox
function _set_messagecount($mbox_name, $mode, $increment)
{
$a_mailbox_cache = FALSE;
$mailbox = $mbox_name ? $mbox_name : $this->mailbox;
$mode = strtoupper($mode);
$a_mailbox_cache = $this->get_cache('messagecount');
if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
return FALSE;
// add incremental value to messagecount
$a_mailbox_cache[$mailbox][$mode] += $increment;
// there's something wrong, delete from cache
if ($a_mailbox_cache[$mailbox][$mode] < 0)
unset($a_mailbox_cache[$mailbox][$mode]);
// write back to cache
$this->update_cache('messagecount', $a_mailbox_cache);
return TRUE;
}
// remove messagecount of a specific mailbox from cache
function _clear_messagecount($mbox_name='')
{
$a_mailbox_cache = FALSE;
$mailbox = $mbox_name ? $mbox_name : $this->mailbox;
$a_mailbox_cache = $this->get_cache('messagecount');
if (is_array($a_mailbox_cache[$mailbox]))
{
unset($a_mailbox_cache[$mailbox]);
$this->update_cache('messagecount', $a_mailbox_cache);
}
}
// split RFC822 header string into an associative array
function _parse_headers($headers)
{
$a_headers = array();
$lines = explode("\n", $headers);
$c = count($lines);
for ($i=0; $i<$c; $i++)
{
if ($p = strpos($lines[$i], ': '))
{
$field = strtolower(substr($lines[$i], 0, $p));
$value = trim(substr($lines[$i], $p+1));
if (!empty($value))
$a_headers[$field] = $value;
}
}
return $a_headers;
}
function _parse_address_list($str)
{
// remove any newlines and carriage returns before
$a = $this->_explode_quoted_string(',', preg_replace( "/[\r\n]/", " ", $str));
$result = array();
foreach ($a as $key => $val)
{
$val = str_replace("\"<", "\" <", $val);
$sub_a = $this->_explode_quoted_string(' ', $this->decode_header($val));
$result[$key]['name'] = '';
foreach ($sub_a as $k => $v)
{
if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0))
$result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
else
$result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
}
if (empty($result[$key]['name']))
$result[$key]['name'] = $result[$key]['address'];
}
return $result;
}
function _explode_quoted_string($delimiter, $string)
{
$quotes = explode("\"", $string);
foreach ($quotes as $key => $val)
if (($key % 2) == 1)
$quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
$string = implode("\"", $quotes);
$result = explode($delimiter, $string);
foreach ($result as $key => $val)
$result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
return $result;
}
}
/**
* Class representing a message part
*/
class rcube_message_part
{
var $mime_id = '';
var $ctype_primary = 'text';
var $ctype_secondary = 'plain';
var $mimetype = 'text/plain';
var $disposition = '';
var $encoding = '8bit';
var $charset = '';
var $size = 0;
var $headers = array();
var $d_parameters = array();
var $ctype_parameters = array();
}
/**
* rcube_header_sorter
*
* Class for sorting an array of iilBasicHeader objects in a predetermined order.
*
* @author Eric Stadtherr
*/
class rcube_header_sorter
{
var $sequence_numbers = array();
/**
* set the predetermined sort order.
*
* @param array $seqnums numerically indexed array of IMAP message sequence numbers
*/
function set_sequence_numbers($seqnums)
{
$this->sequence_numbers = $seqnums;
}
/**
* sort the array of header objects
*
* @param array $headers array of iilBasicHeader objects indexed by UID
*/
function sort_headers(&$headers)
{
/*
* uksort would work if the keys were the sequence number, but unfortunately
* the keys are the UIDs. We'll use uasort instead and dereference the value
* to get the sequence number (in the "id" field).
*
* uksort($headers, array($this, "compare_seqnums"));
*/
uasort($headers, array($this, "compare_seqnums"));
}
/**
* get the position of a message sequence number in my sequence_numbers array
*
* @param integer $seqnum message sequence number contained in sequence_numbers
*/
function position_of($seqnum)
{
$c = count($this->sequence_numbers);
for ($pos = 0; $pos <= $c; $pos++)
{
if ($this->sequence_numbers[$pos] == $seqnum)
return $pos;
}
return -1;
}
/**
* Sort method called by uasort()
*/
function compare_seqnums($a, $b)
{
// First get the sequence number from the header object (the 'id' field).
$seqa = $a->id;
$seqb = $b->id;
// then find each sequence number in my ordered list
$posa = $this->position_of($seqa);
$posb = $this->position_of($seqb);
// return the relative position as the comparison value
$ret = $posa - $posb;
return $ret;
}
}
/**
* Add quoted-printable encoding to a given string
*
* @param string $input string to encode
* @param int $line_max add new line after this number of characters
* @param boolena $space_conf true if spaces should be converted into =20
* @return encoded string
*/
function quoted_printable_encode($input, $line_max=76, $space_conv=false)
{
$hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
$lines = preg_split("/(?:\r\n|\r|\n)/", $input);
$eol = "\r\n";
$escape = "=";
$output = "";
while( list(, $line) = each($lines))
{
//$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
$linlen = strlen($line);
$newline = "";
for($i = 0; $i < $linlen; $i++)
{
$c = substr( $line, $i, 1 );
$dec = ord( $c );
if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
{
$c = "=2E";
}
if ( $dec == 32 )
{
if ( $i == ( $linlen - 1 ) ) // convert space at eol only
{
$c = "=20";
}
else if ( $space_conv )
{
$c = "=20";
}
}
else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) // always encode "\t", which is *not* required
{
$h2 = floor($dec/16);
$h1 = floor($dec%16);
$c = $escape.$hex["$h2"].$hex["$h1"];
}
if ( (strlen($newline) + strlen($c)) >= $line_max ) // CRLF is not counted
{
$output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
$newline = "";
// check if newline first character will be point or not
if ( $dec == 46 )
{
$c = "=2E";
}
}
$newline .= $c;
} // end of for
$output .= $newline.$eol;
} // end of while
return trim($output);
}
?>
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 522a1c2bb..a44d81a1d 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1,1504 +1,1503 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/func.inc |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2005, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Provide webmail functionality and GUI objects |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
require_once('lib/html2text.inc');
require_once('lib/enriched.inc');
$EMAIL_ADDRESS_PATTERN = '/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i';
if (empty($_SESSION['mbox'])){
$_SESSION['mbox'] = $IMAP->get_mailbox_name();
}
// set imap properties and session vars
if (strlen($_GET['_mbox']))
{
$IMAP->set_mailbox($_GET['_mbox']);
$_SESSION['mbox'] = $_GET['_mbox'];
}
if (strlen($_GET['_page']))
{
$IMAP->set_page($_GET['_page']);
$_SESSION['page'] = $_GET['_page'];
}
// set mailbox to INBOX if not set
if (empty($_SESSION['mbox']))
$_SESSION['mbox'] = $IMAP->get_mailbox_name();
// set default sort col/order to session
if (!isset($_SESSION['sort_col']))
$_SESSION['sort_col'] = $CONFIG['message_sort_col'];
if (!isset($_SESSION['sort_order']))
$_SESSION['sort_order'] = $CONFIG['message_sort_order'];
// set message set for search result
if (!empty($_GET['_search']) && isset($_SESSION['search'][$_GET['_search']]))
$IMAP->set_search_set($_SESSION['search'][$_GET['_search']]);
// define url for getting message parts
if (strlen($_GET['_uid']))
$GET_URL = sprintf('%s&_action=get&_mbox=%s&_uid=%d', $COMM_PATH, $IMAP->get_mailbox_name(), $_GET['_uid']);
// set current mailbox in client environment
$OUTPUT->add_script(sprintf("%s.set_env('mailbox', '%s');", $JS_OBJECT_NAME, $IMAP->get_mailbox_name()));
if ($CONFIG['trash_mbox'])
$OUTPUT->add_script(sprintf("%s.set_env('trash_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['trash_mbox']));
if ($CONFIG['drafts_mbox'])
$OUTPUT->add_script(sprintf("%s.set_env('drafts_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['drafts_mbox']));
if ($CONFIG['junk_mbox'])
$OUTPUT->add_script(sprintf("%s.set_env('junk_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['junk_mbox']));
// return the mailboxlist in HTML
function rcmail_mailbox_list($attrib)
{
global $IMAP, $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $COMM_PATH;
static $s_added_script = FALSE;
static $a_mailboxes;
// add some labels to client
rcube_add_label('purgefolderconfirm');
rcube_add_label('deletemessagesconfirm');
// $mboxlist_start = rcube_timer();
$type = $attrib['type'] ? $attrib['type'] : 'ul';
$add_attrib = $type=='select' ? array('style', 'class', 'id', 'name', 'onchange') :
array('style', 'class', 'id');
if ($type=='ul' && !$attrib['id'])
$attrib['id'] = 'rcmboxlist';
// allow the following attributes to be added to the <ul> tag
$attrib_str = create_attrib_string($attrib, $add_attrib);
$out = '<' . $type . $attrib_str . ">\n";
// add no-selection option
if ($type=='select' && $attrib['noselection'])
$out .= sprintf('<option value="0">%s</option>'."\n",
rcube_label($attrib['noselection']));
// get mailbox list
$mbox_name = $IMAP->get_mailbox_name();
// for these mailboxes we have localized labels
$special_mailboxes = array('inbox', 'sent', 'drafts', 'trash', 'junk');
// build the folders tree
if (empty($a_mailboxes))
{
// get mailbox list
$a_folders = $IMAP->list_mailboxes();
$delimiter = $IMAP->get_hierarchy_delimiter();
$a_mailboxes = array();
// rcube_print_time($mboxlist_start, 'list_mailboxes()');
foreach ($a_folders as $folder)
rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
}
// var_dump($a_mailboxes);
if ($type=='select')
$out .= rcmail_render_folder_tree_select($a_mailboxes, $special_mailboxes, $mbox_name, $attrib['maxlength']);
else
$out .= rcmail_render_folder_tree_html($a_mailboxes, $special_mailboxes, $mbox_name, $attrib['maxlength']);
// rcube_print_time($mboxlist_start, 'render_folder_tree()');
if ($type=='ul')
$OUTPUT->add_script(sprintf("%s.gui_object('mailboxlist', '%s');", $JS_OBJECT_NAME, $attrib['id']));
return $out . "</$type>";
}
// create a hierarchical array of the mailbox list
function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
{
$pos = strpos($folder, $delm);
if ($pos !== false)
{
$subFolders = substr($folder, $pos+1);
$currentFolder = substr($folder, 0, $pos);
}
else
{
$subFolders = false;
$currentFolder = $folder;
}
$path .= $currentFolder;
if (!isset($arrFolders[$currentFolder]))
{
$arrFolders[$currentFolder] = array('id' => $path,
'name' => rcube_charset_convert($currentFolder, 'UTF-7'),
'folders' => array());
}
if (!empty($subFolders))
rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
}
// return html for a structured list <ul> for the mailbox tree
function rcmail_render_folder_tree_html(&$arrFolders, &$special, &$mbox_name, $maxlength, $nestLevel=0)
{
global $JS_OBJECT_NAME, $COMM_PATH, $IMAP, $CONFIG, $OUTPUT;
$idx = 0;
$out = '';
foreach ($arrFolders as $key => $folder)
{
$zebra_class = ($nestLevel*$idx)%2 ? 'even' : 'odd';
$title = '';
$folder_lc = strtolower($folder['id']);
if (in_array($folder_lc, $special))
$foldername = rcube_label($folder_lc);
else
{
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength>1)
{
$fname = abbrevate_string($foldername, $maxlength);
if ($fname != $foldername)
$title = ' title="'.Q($foldername).'"';
$foldername = $fname;
}
}
// add unread message count display
if ($unread_count = $IMAP->messagecount($folder['id'], 'RECENT', ($folder['id']==$mbox_name)))
$foldername .= sprintf(' (%d)', $unread_count);
// make folder name safe for ids and class names
$folder_css = $class_name = preg_replace('/[^a-z0-9\-_]/', '', $folder_lc);
// set special class for Sent, Drafts, Trash and Junk
if ($folder['id']==$CONFIG['sent_mbox'])
$class_name = 'sent';
else if ($folder['id']==$CONFIG['drafts_mbox'])
$class_name = 'drafts';
else if ($folder['id']==$CONFIG['trash_mbox'])
$class_name = 'trash';
else if ($folder['id']==$CONFIG['junk_mbox'])
$class_name = 'junk';
$js_name = htmlspecialchars(JQ($folder['id']));
$out .= sprintf('<li id="rcmbx%s" class="mailbox %s %s%s%s"><a href="%s&amp;_mbox=%s"'.
' onclick="return %s.command(\'list\',\'%s\')"'.
' onmouseover="return %s.focus_mailbox(\'%s\')"' .
' onmouseout="return %s.unfocus_mailbox(\'%s\')"' .
' onmouseup="return %s.mbox_mouse_up(\'%s\')"%s>%s</a>',
$folder_css,
$class_name,
$zebra_class,
$unread_count ? ' unread' : '',
$folder['id']==$mbox_name ? ' selected' : '',
$COMM_PATH,
urlencode($folder['id']),
$JS_OBJECT_NAME,
$js_name,
$JS_OBJECT_NAME,
$js_name,
$JS_OBJECT_NAME,
$js_name,
$JS_OBJECT_NAME,
$js_name,
$title,
Q($foldername));
if (!empty($folder['folders']))
$out .= "\n<ul>\n" . rcmail_render_folder_tree_html($folder['folders'], $special, $mbox_name, $maxlength, $nestLevel+1) . "</ul>\n";
$out .= "</li>\n";
$idx++;
}
return $out;
}
// return html for a flat list <select> for the mailbox tree
function rcmail_render_folder_tree_select(&$arrFolders, &$special, &$mbox_name, $maxlength, $nestLevel=0)
{
global $IMAP, $OUTPUT;
$idx = 0;
$out = '';
foreach ($arrFolders as $key=>$folder)
{
$folder_lc = strtolower($folder['id']);
if (in_array($folder_lc, $special))
$foldername = rcube_label($folder_lc);
else
{
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength>1)
$foldername = abbrevate_string($foldername, $maxlength);
}
$out .= sprintf('<option value="%s">%s%s</option>'."\n",
htmlspecialchars($folder['id']),
str_repeat('&nbsp;', $nestLevel*4),
Q($foldername));
if (!empty($folder['folders']))
$out .= rcmail_render_folder_tree_select($folder['folders'], $special, $mbox_name, $maxlength, $nestLevel+1);
$idx++;
}
return $out;
}
// return the message list as HTML table
function rcmail_message_list($attrib)
{
global $IMAP, $CONFIG, $COMM_PATH, $OUTPUT, $JS_OBJECT_NAME;
$skin_path = $CONFIG['skin_path'];
$image_tag = '<img src="%s%s" alt="%s" border="0" />';
// check to see if we have some settings for sorting
$sort_col = $_SESSION['sort_col'];
$sort_order = $_SESSION['sort_order'];
// add some labels to client
rcube_add_label('from', 'to');
// get message headers
$a_headers = $IMAP->list_headers('', '', $sort_col, $sort_order);
// add id to message list table if not specified
if (!strlen($attrib['id']))
$attrib['id'] = 'rcubemessagelist';
// allow the following attributes to be added to the <table> tag
$attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
$out = '<table' . $attrib_str . ">\n";
// define list of cols to be displayed
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
$a_sort_cols = array('subject', 'date', 'from', 'to', 'size');
// show 'to' instead of from in sent messages
if (($IMAP->get_mailbox_name()==$CONFIG['sent_mbox'] || $IMAP->get_mailbox_name()==$CONFIG['drafts_mbox']) && ($f = array_search('from', $a_show_cols))
&& !array_search('to', $a_show_cols))
$a_show_cols[$f] = 'to';
// add col definition
$out .= '<colgroup>';
$out .= '<col class="icon" />';
foreach ($a_show_cols as $col)
$out .= sprintf('<col class="%s" />', $col);
$out .= '<col class="icon" />';
$out .= "</colgroup>\n";
// add table title
$out .= "<thead><tr>\n<td class=\"icon\">&nbsp;</td>\n";
$javascript = '';
foreach ($a_show_cols as $col)
{
// get column name
$col_name = Q(rcube_label($col));
// make sort links
$sort = '';
if ($IMAP->get_capability('sort') && in_array($col, $a_sort_cols))
{
// have buttons configured
if (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton']))
{
$sort = '&nbsp;&nbsp;';
// asc link
if (!empty($attrib['sortascbutton']))
{
$sort .= rcube_button(array('command' => 'sort',
'prop' => $col.'_ASC',
'image' => $attrib['sortascbutton'],
'align' => 'absmiddle',
'title' => 'sortasc'));
}
// desc link
if (!empty($attrib['sortdescbutton']))
{
$sort .= rcube_button(array('command' => 'sort',
'prop' => $col.'_DESC',
'image' => $attrib['sortdescbutton'],
'align' => 'absmiddle',
'title' => 'sortdesc'));
}
}
// just add a link tag to the header
else
{
$col_name = sprintf('<a href="./#sort" onclick="return %s.command(\'sort\',\'%s\',this)" title="%s">%s</a>',
$JS_OBJECT_NAME,
$col,
rcube_label('sortby'),
$col_name);
}
}
$sort_class = $col==$sort_col ? " sorted$sort_order" : '';
// put it all together
$out .= '<td class="'.$col.$sort_class.'" id="rcmHead'.$col.'">' . "$col_name$sort</td>\n";
}
$out .= '<td class="icon">'.($attrib['attachmenticon'] ? sprintf($image_tag, $skin_path, $attrib['attachmenticon'], '') : '')."</td>\n";
$out .= "</tr></thead>\n<tbody>\n";
// no messages in this mailbox
if (!sizeof($a_headers))
{
$out .= sprintf('<tr><td colspan="%d">%s</td></tr>',
sizeof($a_show_cols)+2,
Q(rcube_label('nomessagesfound')));
}
$a_js_message_arr = array();
// create row for each message
foreach ($a_headers as $i => $header) //while (list($i, $header) = each($a_headers))
{
$message_icon = $attach_icon = '';
$js_row_arr = array();
$zebra_class = $i%2 ? 'even' : 'odd';
// set messag attributes to javascript array
if ($header->deleted)
$js_row_arr['deleted'] = true;
if (!$header->seen)
$js_row_arr['unread'] = true;
if ($header->answered)
$js_row_arr['replied'] = true;
// set message icon
if ($attrib['deletedicon'] && $header->deleted)
$message_icon = $attrib['deletedicon'];
else if ($attrib['unreadicon'] && !$header->seen)
$message_icon = $attrib['unreadicon'];
else if ($attrib['repliedicon'] && $header->answered)
$message_icon = $attrib['repliedicon'];
else if ($attrib['messageicon'])
$message_icon = $attrib['messageicon'];
// set attachment icon
if ($attrib['attachmenticon'] && preg_match("/multipart\/[mr]/i", $header->ctype))
$attach_icon = $attrib['attachmenticon'];
$out .= sprintf('<tr id="rcmrow%d" class="message%s%s %s">'."\n",
$header->uid,
$header->seen ? '' : ' unread',
$header->deleted ? ' deleted' : '',
$zebra_class);
$out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : '');
// format each col
foreach ($a_show_cols as $col)
{
if ($col=='from' || $col=='to')
$cont = Q(rcmail_address_string($header->$col, 3, $attrib['addicon']), 'show');
else if ($col=='subject')
{
$cont = Q($IMAP->decode_header($header->$col));
// firefox/mozilla temporary workaround to pad subject with content so that whitespace in rows responds to drag+drop
$cont .= '<img src="./program/blank.gif" height="5" width="1000" alt="" />';
}
else if ($col=='size')
$cont = show_bytes($header->$col);
else if ($col=='date')
$cont = format_date($header->date); //date('m.d.Y G:i:s', strtotime($header->date));
else
$cont = Q($header->$col);
$out .= '<td class="'.$col.'">' . $cont . "</td>\n";
}
$out .= sprintf("<td class=\"icon\">%s</td>\n", $attach_icon ? sprintf($image_tag, $skin_path, $attach_icon, '') : '');
$out .= "</tr>\n";
if (sizeof($js_row_arr))
$a_js_message_arr[$header->uid] = $js_row_arr;
}
// complete message table
$out .= "</tbody></table>\n";
$message_count = $IMAP->messagecount();
// set client env
$javascript .= sprintf("%s.gui_object('mailcontframe', '%s');\n", $JS_OBJECT_NAME, 'mailcontframe');
$javascript .= sprintf("%s.gui_object('messagelist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
$javascript .= sprintf("%s.set_env('messagecount', %d);\n", $JS_OBJECT_NAME, $message_count);
$javascript .= sprintf("%s.set_env('current_page', %d);\n", $JS_OBJECT_NAME, $IMAP->list_page);
$javascript .= sprintf("%s.set_env('pagecount', %d);\n", $JS_OBJECT_NAME, ceil($message_count/$IMAP->page_size));
$javascript .= sprintf("%s.set_env('sort_col', '%s');\n", $JS_OBJECT_NAME, $sort_col);
$javascript .= sprintf("%s.set_env('sort_order', '%s');\n", $JS_OBJECT_NAME, $sort_order);
if ($attrib['messageicon'])
$javascript .= sprintf("%s.set_env('messageicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['messageicon']);
if ($attrib['deletedicon'])
$javascript .= sprintf("%s.set_env('deletedicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['deletedicon']);
if ($attrib['unreadicon'])
$javascript .= sprintf("%s.set_env('unreadicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['unreadicon']);
if ($attrib['repliedicon'])
$javascript .= sprintf("%s.set_env('repliedicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['repliedicon']);
if ($attrib['attachmenticon'])
$javascript .= sprintf("%s.set_env('attachmenticon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['attachmenticon']);
$javascript .= sprintf("%s.set_env('messages', %s);", $JS_OBJECT_NAME, array2js($a_js_message_arr));
$OUTPUT->add_script($javascript);
$OUTPUT->include_script('list.js');
return $out;
}
// return javascript commands to add rows to the message list
function rcmail_js_message_list($a_headers, $insert_top=FALSE)
{
global $CONFIG, $IMAP;
$commands = '';
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
// show 'to' instead of from in sent messages
if (($IMAP->get_mailbox_name()==$CONFIG['sent_mbox'] || $IMAP->get_mailbox_name()==$CONFIG['drafts_mbox'])
&& ($f = array_search('from', $a_show_cols)) && !array_search('to', $a_show_cols))
$a_show_cols[$f] = 'to';
$commands .= sprintf("this.set_message_coltypes(%s);\n", array2js($a_show_cols));
// loop through message headers
for ($n=0; $a_headers[$n]; $n++)
{
$header = $a_headers[$n];
$a_msg_cols = array();
$a_msg_flags = array();
// format each col; similar as in rcmail_message_list()
foreach ($a_show_cols as $col)
{
if ($col=='from' || $col=='to')
$cont = Q(rcmail_address_string($header->$col, 3), 'show');
else if ($col=='subject')
$cont = Q($IMAP->decode_header($header->$col));
else if ($col=='size')
$cont = show_bytes($header->$col);
else if ($col=='date')
$cont = format_date($header->date); //date('m.d.Y G:i:s', strtotime($header->date));
else
$cont = Q($header->$col);
$a_msg_cols[$col] = $cont;
}
$a_msg_flags['deleted'] = $header->deleted ? 1 : 0;
$a_msg_flags['unread'] = $header->seen ? 0 : 1;
$a_msg_flags['replied'] = $header->answered ? 1 : 0;
$commands .= sprintf("this.add_message_row(%s, %s, %s, %b, %b);\n",
$header->uid,
array2js($a_msg_cols),
array2js($a_msg_flags),
preg_match("/multipart\/m/i", $header->ctype),
$insert_top);
}
return $commands;
}
// return an HTML iframe for loading mail content
function rcmail_messagecontent_frame($attrib)
{
global $OUTPUT, $JS_OBJECT_NAME;
if (empty($attrib['id']))
$attrib['id'] = 'rcmailcontentwindow';
// allow the following attributes to be added to the <iframe> tag
$attrib_str = create_attrib_string($attrib, array('id', 'class', 'style', 'src', 'width', 'height', 'frameborder'));
$framename = $attrib['id'];
$out = sprintf('<iframe name="%s"%s></iframe>'."\n",
$framename,
$attrib_str);
$OUTPUT->add_script("$JS_OBJECT_NAME.set_env('contentframe', '$framename');");
return $out;
}
// return code for search function
function rcmail_search_form($attrib)
{
global $OUTPUT, $JS_OBJECT_NAME;
// add some labels to client
rcube_add_label('searching');
$attrib['name'] = '_q';
if (empty($attrib['id']))
$attrib['id'] = 'rcmqsearchbox';
$input_q = new textfield($attrib);
$out = $input_q->show();
$OUTPUT->add_script(sprintf("%s.gui_object('qsearchbox', '%s');",
$JS_OBJECT_NAME,
$attrib['id']));
// add form tag around text field
if (empty($attrib['form']))
$out = sprintf('<form name="rcmqsearchform" action="./" '.
'onsubmit="%s.command(\'search\');return false" style="display:inline;">%s</form>',
$JS_OBJECT_NAME,
$out);
return $out;
}
function rcmail_messagecount_display($attrib)
{
global $IMAP, $OUTPUT, $JS_OBJECT_NAME;
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$OUTPUT->add_script(sprintf("%s.gui_object('countdisplay', '%s');",
$JS_OBJECT_NAME,
$attrib['id']));
// allow the following attributes to be added to the <span> tag
$attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
$out = '<span' . $attrib_str . '>';
$out .= rcmail_get_messagecount_text();
$out .= '</span>';
return $out;
}
function rcmail_quota_display($attrib)
{
global $OUTPUT, $JS_OBJECT_NAME, $COMM_PATH;
if (!$attrib['id'])
$attrib['id'] = 'rcmquotadisplay';
$OUTPUT->add_script(sprintf("%s.gui_object('quotadisplay', '%s');", $JS_OBJECT_NAME, $attrib['id']));
// allow the following attributes to be added to the <span> tag
$attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
$out = '<span' . $attrib_str . '>';
$out .= rcmail_quota_content($attrib['display']);
$out .= '</span>';
return $out;
}
function rcmail_quota_content($display)
{
global $IMAP, $COMM_PATH;
if (!$IMAP->get_capability('QUOTA'))
$quota_text = rcube_label('unknown');
else if ($quota = $IMAP->get_quota())
{
$quota_text = sprintf("%s / %s (%.0f%%)",
show_bytes($quota["used"] * 1024),
show_bytes($quota["total"] * 1024),
$quota["percent"]);
// show quota as image (by Brett Patterson)
if ($display == 'image' && function_exists('imagegif'))
{
$attrib = array('width' => 100, 'height' => 14);
$quota_text = sprintf('<img src="%s&amp;_action=quotaimg&amp;u=%s&amp;q=%d&amp;w=%d&amp;h=%d" width="%d" height="%d" alt="%s" title="%s / %s" />',
$COMM_PATH,
$quota['used'], $quota['total'],
$attrib['width'], $attrib['height'],
$attrib['width'], $attrib['height'],
$quota_text,
show_bytes($quota["used"] * 1024),
show_bytes($quota["total"] * 1024));
}
}
else
$quota_text = rcube_label('unlimited');
return $quota_text;
}
function rcmail_get_messagecount_text($count=NULL, $page=NULL)
{
global $IMAP, $MESSAGE;
if (isset($MESSAGE['index']))
{
return rcube_label(array('name' => 'messagenrof',
'vars' => array('nr' => $MESSAGE['index']+1,
'count' => $count!==NULL ? $count : $IMAP->messagecount())));
}
if ($page===NULL)
$page = $IMAP->list_page;
$start_msg = ($page-1) * $IMAP->page_size + 1;
$max = $count!==NULL ? $count : $IMAP->messagecount();
if ($max==0)
$out = rcube_label('mailboxempty');
else
$out = rcube_label(array('name' => 'messagesfromto',
'vars' => array('from' => $start_msg,
'to' => min($max, $start_msg + $IMAP->page_size - 1),
'count' => $max)));
return Q($out);
}
function rcmail_print_body($part, $safe=FALSE, $plain=FALSE)
{
global $IMAP, $REMOTE_OBJECTS, $JS_OBJECT_NAME;
$body = is_array($part->replaces) ? strtr($part->body, $part->replaces) : $part->body;
// text/html
if ($part->ctype_secondary=='html')
{
// remove charset specification in HTML message
$body = preg_replace('/charset=[a-z0-9\-]+/i', '', $body);
if (!$safe) // remove remote images and scripts
{
$remote_patterns = array('/<img\s+(.*)src=(["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)(\2|\s|>)/Ui',
'/(src|background)=(["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)(\2|\s|>)/Ui',
'/(<base.*href=["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)([^<]*>)/i',
'/(<link.*href=["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)([^<]*>)/i',
'/url\s*\(["\']?([hftps]{3,5}:\/{2}[^"\'\s]+)["\']?\)/i',
'/url\s*\(["\']?([\.\/]+[^"\'\s]+)["\']?\)/i',
'/<script.+<\/script>/Umis');
$remote_replaces = array('<img \\1src=\\2./program/blocked.gif\\4',
'',
'',
'',
'none',
'none',
'');
// set flag if message containes remote obejcts that where blocked
foreach ($remote_patterns as $pattern)
{
if (preg_match($pattern, $body))
{
$REMOTE_OBJECTS = TRUE;
break;
}
}
$body = preg_replace($remote_patterns, $remote_replaces, $body);
}
return Q($body, 'show', FALSE);
}
// text/enriched
if ($part->ctype_secondary=='enriched')
{
return Q(enriched_to_html($body), 'show');
}
else
{
// make links and email-addresses clickable
$convert_patterns = $convert_replaces = $replace_strings = array();
$url_chars = 'a-z0-9_\-\+\*\$\/&%=@#:;';
$url_chars_within = '\?\.~,!';
$convert_patterns[] = "/([\w]+):\/\/([a-z0-9\-\.]+[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/ie";
$convert_replaces[] = "rcmail_str_replacement('<a href=\"\\1://\\2\" target=\"_blank\">\\1://\\2</a>', \$replace_strings)";
$convert_patterns[] = "/([^\/:]|\s)(www\.)([a-z0-9\-]{2,}[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/ie";
$convert_replaces[] = "rcmail_str_replacement('\\1<a href=\"http://\\2\\3\" target=\"_blank\">\\2\\3</a>', \$replace_strings)";
$convert_patterns[] = '/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/ie';
$convert_replaces[] = "rcmail_str_replacement('<a href=\"mailto:\\1\" onclick=\"return $JS_OBJECT_NAME.command(\'compose\',\'\\1\',this)\">\\1</a>', \$replace_strings)";
if ($part->ctype_parameters['format'] != 'flowed')
$body = wordwrap(trim($body), 80);
$body = preg_replace($convert_patterns, $convert_replaces, $body);
// split body into single lines
$a_lines = preg_split('/\r?\n/', $body);
$quote_level = 0;
// colorize quoted parts
for($n=0; $n<sizeof($a_lines); $n++)
{
$line = $a_lines[$n];
$quotation = '';
$q = 0;
if (preg_match('/^(>+\s*)/', $line, $regs))
{
$q = strlen(preg_replace('/\s/', '', $regs[1]));
$line = substr($line, strlen($regs[1]));
if ($q > $quote_level)
$quotation = str_repeat('<blockquote>', $q - $quote_level);
else if ($q < $quote_level)
$quotation = str_repeat("</blockquote>", $quote_level - $q);
}
else if ($quote_level > 0)
$quotation = str_repeat("</blockquote>", $quote_level);
$quote_level = $q;
$a_lines[$n] = $quotation . Q($line, 'replace', FALSE);
}
// insert the links for urls and mailtos
$body = preg_replace("/##string_replacement\{([0-9]+)\}##/e", "\$replace_strings[\\1]", join("\n", $a_lines));
return "<div class=\"pre\">".$body."\n</div>";
}
}
// add a string to the replacement array and return a replacement string
function rcmail_str_replacement($str, &$rep)
{
static $count = 0;
$rep[$count] = stripslashes($str);
return "##string_replacement{".($count++)."}##";
}
function rcmail_parse_message(&$structure, $arg=array(), $recursive=FALSE)
{
global $IMAP;
static $sa_inline_objects = array();
// arguments are: (bool)$prefer_html, (string)$get_url
extract($arg);
$a_attachments = array();
$a_return_parts = array();
$out = '';
$message_ctype_primary = strtolower($structure->ctype_primary);
$message_ctype_secondary = strtolower($structure->ctype_secondary);
// show message headers
if ($recursive && is_array($structure->headers) && isset($structure->headers['subject']))
{
$c = new stdClass;
$c->type = 'headers';
$c->headers = &$structure->headers;
$a_return_parts[] = $c;
}
// print body if message doesn't have multiple parts
if ($message_ctype_primary=='text')
{
$structure->type = 'content';
$a_return_parts[] = &$structure;
}
// message contains alternative parts
else if ($message_ctype_primary=='multipart' && $message_ctype_secondary=='alternative' && is_array($structure->parts))
{
// get html/plaintext parts
$plain_part = $html_part = $print_part = $related_part = NULL;
foreach ($structure->parts as $p => $sub_part)
{
$sub_ctype_primary = strtolower($sub_part->ctype_primary);
$sub_ctype_secondary = strtolower($sub_part->ctype_secondary);
// check if sub part is
if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='plain')
$plain_part = $p;
else if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='html')
$html_part = $p;
else if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='enriched')
$enriched_part = $p;
else if ($sub_ctype_primary=='multipart' && $sub_ctype_secondary=='related')
$related_part = $p;
}
// parse related part (alternative part could be in here)
if ($related_part!==NULL && $prefer_html)
{
list($parts, $attachmnts) = rcmail_parse_message($structure->parts[$related_part], $arg, TRUE);
$a_return_parts = array_merge($a_return_parts, $parts);
$a_attachments = array_merge($a_attachments, $attachmnts);
}
// print html/plain part
else if ($html_part!==NULL && $prefer_html)
$print_part = &$structure->parts[$html_part];
else if ($enriched_part!==NULL)
$print_part = &$structure->parts[$enriched_part];
else if ($plain_part!==NULL)
$print_part = &$structure->parts[$plain_part];
// show message body
if (is_object($print_part))
{
$print_part->type = 'content';
$a_return_parts[] = $print_part;
}
// show plaintext warning
else if ($html_part!==NULL)
{
$c = new stdClass;
$c->type = 'content';
$c->body = rcube_label('htmlmessage');
$c->ctype_primary = 'text';
$c->ctype_secondary = 'plain';
$a_return_parts[] = $c;
}
// add html part as attachment
if ($html_part!==NULL && $structure->parts[$html_part]!==$print_part)
{
$html_part = &$structure->parts[$html_part];
$html_part->filename = rcube_label('htmlmessage');
$html_part->mimetype = 'text/html';
$a_attachments[] = $html_part;
}
}
// message contains multiple parts
else if (is_array($structure->parts) && !empty($structure->parts))
{
for ($i=0; $i<count($structure->parts); $i++)
{
$mail_part = &$structure->parts[$i];
$primary_type = strtolower($mail_part->ctype_primary);
$secondary_type = strtolower($mail_part->ctype_secondary);
// multipart/alternative
if ($primary_type=='multipart')
{
list($parts, $attachmnts) = rcmail_parse_message($mail_part, $arg, TRUE);
$a_return_parts = array_merge($a_return_parts, $parts);
$a_attachments = array_merge($a_attachments, $attachmnts);
}
// part text/[plain|html] OR message/delivery-status
else if (($primary_type=='text' && ($secondary_type=='plain' || $secondary_type=='html') && $mail_part->disposition!='attachment') ||
($primary_type=='message' && $secondary_type=='delivery-status'))
{
$mail_part->type = 'content';
$a_return_parts[] = $mail_part;
}
// part message/*
else if ($primary_type=='message')
{
list($parts, $attachmnts) = rcmail_parse_message($mail_part, $arg, TRUE);
$a_return_parts = array_merge($a_return_parts, $parts);
$a_attachments = array_merge($a_attachments, $attachmnts);
}
// part is file/attachment
else if ($mail_part->disposition=='attachment' || $mail_part->disposition=='inline' || $mail_part->headers['content-id'] ||
(empty($mail_part->disposition) && ($mail_part->d_parameters['filename'] || $mail_part->ctype_parameters['name'])))
{
- // skip apple ressource files
+ // skip apple resource forks
if ($message_ctype_secondary=='appledouble' && $secondary_type=='applefile')
continue;
// part belongs to a related message
if ($message_ctype_secondary=='related' && $mail_part->headers['content-id'])
{
$mail_part->filename = rcube_imap::decode_mime_string($mail_part->d_parameters['filename']);
$mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']);
$sa_inline_objects[] = $mail_part;
}
// is regular attachment
else if (($fname = $mail_part->d_parameters['filename']) ||
($fname = $mail_part->ctype_parameters['name']) ||
($fname = $mail_part->headers['content-description']))
{
$mail_part->filename = rcube_imap::decode_mime_string($fname);
$a_attachments[] = $mail_part;
}
}
}
-
// if this was a related part try to resolve references
if ($message_ctype_secondary=='related' && sizeof($sa_inline_objects))
{
$a_replaces = array();
foreach ($sa_inline_objects as $inline_object)
$a_replaces['cid:'.$inline_object->content_id] = htmlspecialchars(sprintf($get_url, $inline_object->mime_id));
// add replace array to each content part
// (will be applied later when part body is available)
for ($i=0; $i<count($a_return_parts); $i++)
{
if ($a_return_parts[$i]->type=='content')
$a_return_parts[$i]->replaces = $a_replaces;
}
}
}
// message is single part non-text
else
{
if (($fname = $structure->d_parameters['filename']) ||
($fname = $structure->ctype_parameters['name']) ||
($fname = $structure->headers['content-description']))
{
$structure->filename = rcube_imap::decode_mime_string($fname);
$a_attachments[] = $structure;
}
}
return array($a_return_parts, $a_attachments);
}
// return table with message headers
function rcmail_message_headers($attrib, $headers=NULL)
{
global $IMAP, $OUTPUT, $MESSAGE;
static $sa_attrib;
// keep header table attrib
if (is_array($attrib) && !$sa_attrib)
$sa_attrib = $attrib;
else if (!is_array($attrib) && is_array($sa_attrib))
$attrib = $sa_attrib;
if (!isset($MESSAGE))
return FALSE;
// get associative array of headers object
if (!$headers)
$headers = is_object($MESSAGE['headers']) ? get_object_vars($MESSAGE['headers']) : $MESSAGE['headers'];
$header_count = 0;
// allow the following attributes to be added to the <table> tag
$attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
$out = '<table' . $attrib_str . ">\n";
// show these headers
$standard_headers = array('subject', 'from', 'organization', 'to', 'cc', 'bcc', 'reply-to', 'date');
foreach ($standard_headers as $hkey)
{
if (!$headers[$hkey])
continue;
if ($hkey=='date' && !empty($headers[$hkey]))
$header_value = format_date(strtotime($headers[$hkey]));
else if (in_array($hkey, array('from', 'to', 'cc', 'bcc', 'reply-to')))
$header_value = Q(rcmail_address_string($headers[$hkey], NULL, $attrib['addicon']), 'show');
else
$header_value = Q($IMAP->decode_header($headers[$hkey]));
$out .= "\n<tr>\n";
$out .= '<td class="header-title">'.Q(rcube_label($hkey)).":&nbsp;</td>\n";
$out .= '<td class="'.$hkey.'" width="90%">'.$header_value."</td>\n</tr>";
$header_count++;
}
$out .= "\n</table>\n\n";
return $header_count ? $out : '';
}
function rcmail_message_body($attrib)
{
global $CONFIG, $OUTPUT, $MESSAGE, $IMAP, $GET_URL, $REMOTE_OBJECTS, $JS_OBJECT_NAME;
if (!is_array($MESSAGE['parts']) && !$MESSAGE['body'])
return '';
if (!$attrib['id'])
$attrib['id'] = 'rcmailMsgBody';
$safe_mode = (bool)$_GET['_safe'];
$attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
$out = '<div '. $attrib_str . ">\n";
$header_attrib = array();
foreach ($attrib as $attr => $value)
if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs))
$header_attrib[$regs[1]] = $value;
// this is an ecrypted message
// -> create a plaintext body with the according message
if (!sizeof($MESSAGE['parts']) && $MESSAGE['headers']->ctype=='multipart/encrypted')
{
$p = new stdClass;
$p->type = 'content';
$p->ctype_primary = 'text';
$p->ctype_secondary = 'plain';
$p->body = rcube_label('encryptedmessage');
$MESSAGE['parts'][0] = $p;
}
if ($MESSAGE['parts'])
{
foreach ($MESSAGE['parts'] as $i => $part)
{
if ($part->type=='headers')
$out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers);
else if ($part->type=='content')
{
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 = $IMAP->get_message_part($MESSAGE['UID'], $part->mime_id, $part);
$body = rcmail_print_body($part, $safe_mode);
$out .= '<div class="message-part">';
if ($part->ctype_secondary != 'plain')
$out .= rcmail_mod_html_body($body, $attrib['id']);
else
$out .= $body;
$out .= "</div>\n";
}
}
}
else
$out .= $MESSAGE['body'];
$ctype_primary = strtolower($MESSAGE['structure']->ctype_primary);
$ctype_secondary = strtolower($MESSAGE['structure']->ctype_secondary);
// list images after mail body
if (get_boolean($attrib['showimages']) && $ctype_primary=='multipart' && $ctype_secondary=='mixed' &&
sizeof($MESSAGE['attachments']) && !strstr($message_body, '<html') && strlen($GET_URL))
{
foreach ($MESSAGE['attachments'] as $attach_prop)
{
if (strpos($attach_prop->mimetype, 'image/')===0)
$out .= sprintf("\n<hr />\n<p align=\"center\"><img src=\"%s&amp;_part=%s\" alt=\"%s\" title=\"%s\" /></p>\n",
htmlspecialchars($GET_URL), $attach_prop->mime_id,
$attach_prop->filename,
$attach_prop->filename);
}
}
// tell client that there are blocked remote objects
if ($REMOTE_OBJECTS && !$safe_mode)
$OUTPUT->add_script(sprintf("%s.set_env('blockedobjects', true);", $JS_OBJECT_NAME));
$out .= "\n</div>";
return $out;
}
// modify a HTML message that it can be displayed inside a HTML page
function rcmail_mod_html_body($body, $container_id)
{
// remove any null-byte characters before parsing
$body = preg_replace('/\x00/', '', $body);
$last_style_pos = 0;
$body_lc = strtolower($body);
// find STYLE tags
while (($pos = strpos($body_lc, '<style', $last_style_pos)) && ($pos2 = strpos($body_lc, '</style>', $pos)))
{
$pos = strpos($body_lc, '>', $pos)+1;
// replace all css definitions with #container [def]
$styles = rcmail_mod_css_styles(substr($body, $pos, $pos2-$pos), $container_id);
$body = substr($body, 0, $pos) . $styles . substr($body, $pos2);
$body_lc = strtolower($body);
$last_style_pos = $pos2;
}
// remove SCRIPT tags
foreach (array('script', 'applet', 'object', 'embed', 'iframe') as $tag)
{
while (($pos = strpos($body_lc, '<'.$tag)) && ($pos2 = strpos($body_lc, '</'.$tag.'>', $pos)))
{
$pos2 += strlen('</'.$tag.'>');
$body = substr($body, 0, $pos) . substr($body, $pos2, strlen($body)-$pos2);
$body_lc = strtolower($body);
}
}
// replace event handlers on any object
while ($body != $prev_body)
{
$prev_body = $body;
$body = preg_replace('/(<[^!][^>]*\s)(on[^=>]+)=([^>]+>)/im', '$1__removed=$3', $body);
$body = preg_replace('/(<[^!][^>]*\shref=["\']?)(javascript:)([^>]*?>)/im', '$1null:$3', $body);
}
// resolve <base href>
$base_reg = '/(<base.*href=["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)([^<]*>)/i';
if (preg_match($base_reg, $body, $regs))
{
$base_url = $regs[2];
$body = preg_replace('/(src|background|href)=(["\']?)([\.\/]+[^"\'\s]+)(\2|\s|>)/Uie', "'\\1=\"'.make_absolute_url('\\3', '$base_url').'\"'", $body);
$body = preg_replace('/(url\s*\()(["\']?)([\.\/]+[^"\'\)\s]+)(\2)\)/Uie', "'\\1\''.make_absolute_url('\\3', '$base_url').'\')'", $body);
$body = preg_replace($base_reg, '', $body);
}
// modify HTML links to open a new window if clicked
$body = preg_replace('/<a\s+([^>]+)>/Uie', "rcmail_alter_html_link('\\1');", $body);
// add comments arround html and other tags
$out = preg_replace(array('/(<\/?html[^>]*>)/i',
'/(<\/?head[^>]*>)/i',
'/(<title[^>]*>.*<\/title>)/Ui',
'/(<\/?meta[^>]*>)/i'),
'<!--\\1-->',
$body);
$out = preg_replace(array('/(<body[^>]*>)/i',
'/(<\/body>)/i'),
array('<div class="rcmBody">',
'</div>'),
$out);
return $out;
}
// parse link attributes and set correct target
function rcmail_alter_html_link($in)
{
$in = preg_replace('/=([^("|\s)]+)(\s|$)/', '="\1"', $in);
$attrib = parse_attrib_string($in);
if (stristr((string)$attrib['href'], 'mailto:'))
$attrib['onclick'] = sprintf("return %s.command('compose','%s',this)",
$GLOBALS['JS_OBJECT_NAME'],
JQ(substr($attrib['href'], 7)));
else if (!empty($attrib['href']) && $attrib['href']{0}!='#')
$attrib['target'] = '_blank';
return '<a' . create_attrib_string($attrib, array('href', 'name', 'target', 'onclick', 'id', 'class', 'style', 'title')) . '>';
}
// replace all css definitions with #container [def]
function rcmail_mod_css_styles($source, $container_id)
{
$a_css_values = array();
$last_pos = 0;
// cut out all contents between { and }
while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
{
$key = sizeof($a_css_values);
$a_css_values[$key] = substr($source, $pos+1, $pos2-($pos+1));
$source = substr($source, 0, $pos+1) . "<<str_replacement[$key]>>" . substr($source, $pos2, strlen($source)-$pos2);
$last_pos = $pos+2;
}
// remove html commends and add #container to each tag selector.
// also replace body definition because we also stripped off the <body> tag
$styles = preg_replace(array('/(^\s*<!--)|(-->\s*$)/', '/(^\s*|,\s*|\}\s*)([a-z0-9\._][a-z0-9\.\-_]*)/im', '/<<str_replacement\[([0-9]+)\]>>/e', "/$container_id\s+body/i"),
array('', "\\1#$container_id \\2", "\$a_css_values[\\1]", "$container_id div.rcmBody"),
$source);
return $styles;
}
function rcmail_has_html_part($message_parts)
{
if (!is_array($message_parts))
return FALSE;
// check all message parts
foreach ($message_parts as $pid => $part)
{
$mimetype = strtolower($part->ctype_primary.'/'.$part->ctype_secondary);
if ($mimetype=='text/html')
{
return TRUE;
}
}
return FALSE;
}
// return first HTML part of a message
function rcmail_first_html_part($message_struct)
{
global $IMAP;
if (!is_array($message_struct['parts']))
return FALSE;
$html_part = NULL;
// check all message parts
foreach ($message_struct['parts'] as $pid => $part)
{
$mimetype = strtolower($part->ctype_primary.'/'.$part->ctype_secondary);
if ($mimetype=='text/html')
{
$html_part = $IMAP->get_message_part($message_struct['UID'], $pid, $part);
}
}
if ($html_part)
{
// remove special chars encoding
//$trans = array_flip(get_html_translation_table(HTML_ENTITIES));
//$html_part = strtr($html_part, $trans);
return $html_part;
}
return FALSE;
}
// return first text part of a message
function rcmail_first_text_part($message_struct)
{
global $IMAP;
if (empty($message_struct['parts']))
return $message_struct['UID'] ? $IMAP->get_body($message_struct['UID']) : false;
// check all message parts
foreach ($message_struct['parts'] as $pid => $part)
{
$mimetype = strtolower($part->ctype_primary.'/'.$part->ctype_secondary);
if ($mimetype=='text/plain')
return $IMAP->get_message_part($message_struct['UID'], $pid, $part);
else if ($mimetype=='text/html')
{
$html_part = $IMAP->get_message_part($message_struct['UID'], $pid, $part);
// remove special chars encoding
$trans = array_flip(get_html_translation_table(HTML_ENTITIES));
$html_part = strtr($html_part, $trans);
// create instance of html2text class
$txt = new html2text($html_part);
return $txt->get_text();
}
}
return FALSE;
}
// decode address string and re-format it as HTML links
function rcmail_address_string($input, $max=NULL, $addicon=NULL)
{
global $IMAP, $PRINT_MODE, $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $EMAIL_ADDRESS_PATTERN;
$a_parts = $IMAP->decode_address_list($input);
if (!sizeof($a_parts))
return $input;
$c = count($a_parts);
$j = 0;
$out = '';
foreach ($a_parts as $part)
{
$j++;
if ($PRINT_MODE)
$out .= sprintf('%s &lt;%s&gt;', Q($part['name']), $part['mailto']);
else if (preg_match($EMAIL_ADDRESS_PATTERN, $part['mailto']))
{
$out .= sprintf('<a href="mailto:%s" onclick="return %s.command(\'compose\',\'%s\',this)" class="rcmContactAddress" title="%s">%s</a>',
$part['mailto'],
$JS_OBJECT_NAME,
$part['mailto'],
$part['mailto'],
Q($part['name']));
if ($addicon)
$out .= sprintf('&nbsp;<a href="#add" onclick="return %s.command(\'add-contact\',\'%s\',this)" title="%s"><img src="%s%s" alt="add" border="0" /></a>',
$JS_OBJECT_NAME,
urlencode($part['string']),
rcube_label('addtoaddressbook'),
$CONFIG['skin_path'],
$addicon);
}
else
{
if ($part['name'])
$out .= Q($part['name']);
if ($part['mailto'])
$out .= (strlen($out) ? ' ' : '') . sprintf('&lt;%s&gt;', $part['mailto']);
}
if ($c>$j)
$out .= ','.($max ? '&nbsp;' : ' ');
if ($max && $j==$max && $c>$j)
{
$out .= '...';
break;
}
}
return $out;
}
function rcmail_message_part_controls()
{
global $CONFIG, $IMAP, $MESSAGE;
if (!is_array($MESSAGE) || !is_array($MESSAGE['parts']) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE['parts'][$_GET['_part']])
return '';
$part = &$MESSAGE['parts'][$_GET['_part']];
$attrib_str = create_attrib_string($attrib, array('id', 'class', 'style', 'cellspacing', 'cellpadding', 'border', 'summary'));
$out = '<table '. $attrib_str . ">\n";
$filename = $part->d_parameters['filename'] ? $part->d_parameters['filename'] : $part->ctype_parameters['name'];
$filesize = $part->size;
if ($filename)
{
$out .= sprintf('<tr><td class="title">%s</td><td>%s</td><td>[<a href="./?%s">%s</a>]</tr>'."\n",
Q(rcube_label('filename')),
Q(rcube_imap::decode_mime_string($filename)),
str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']),
Q(rcube_label('download')));
}
if ($filesize)
$out .= sprintf('<tr><td class="title">%s</td><td>%s</td></tr>'."\n",
Q(rcube_label('filesize')),
show_bytes($filesize));
$out .= "\n</table>";
return $out;
}
function rcmail_message_part_frame($attrib)
{
global $MESSAGE;
$part = $MESSAGE['parts'][$_GET['_part']];
$ctype_primary = strtolower($part->ctype_primary);
$attrib['src'] = './?'.str_replace('_frame=', ($ctype_primary=='text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
$attrib_str = create_attrib_string($attrib, array('id', 'class', 'style', 'src', 'width', 'height'));
$out = '<iframe '. $attrib_str . "></iframe>";
return $out;
}
// clear message composing settings
function rcmail_compose_cleanup()
{
if (!isset($_SESSION['compose']))
return;
// remove attachment files from temp dir
if (is_array($_SESSION['compose']['attachments']))
foreach ($_SESSION['compose']['attachments'] as $attachment)
@unlink($attachment['path']);
unset($_SESSION['compose']);
}
-?>
+?>
\ No newline at end of file
diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc
index e1ae281e4..995a4857f 100644
--- a/program/steps/mail/get.inc
+++ b/program/steps/mail/get.inc
@@ -1,145 +1,149 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/get.inc |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2005, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Delivering a specific part of a mail message |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
require_once('Mail/mimeDecode.php');
// show loading page
if ($_GET['_preload'])
{
$url = str_replace('&_preload=1', '', $_SERVER['REQUEST_URI']);
$message = rcube_label('loadingdata');
print "<html>\n<head>\n" .
'<meta http-equiv="refresh" content="0; url='.htmlspecialchars($url).'">' .
"\n</head>\n<body>" .
$message .
"\n</body>\n</html>";
exit;
}
// similar code as in program/steps/mail/show.inc
if ($_GET['_uid'])
{
$MESSAGE = array('UID' => get_input_value('_uid', RCUBE_INPUT_GET));
$MESSAGE['structure'] = $IMAP->get_structure($MESSAGE['UID']);
$MESSAGE['parts'] = $IMAP->get_mime_numbers($MESSAGE['structure']);
}
// show part page
if ($_GET['_frame'])
{
parse_template('messagepart');
exit;
}
else if ($pid = get_input_value('_part', RCUBE_INPUT_GET))
{
if ($part = $MESSAGE['parts'][$pid]);
{
$ctype_primary = strtolower($part->ctype_primary);
$ctype_secondary = strtolower($part->ctype_secondary);
$mimetype = sprintf('%s/%s', $ctype_primary, $ctype_secondary);
$filename = $part->d_parameters['filename'] ? $part->d_parameters['filename'] : $part->ctype_parameters['name'];
// send correct headers for content type and length
if ($_GET['_download'])
{
// send download headers
header("Content-Type: application/octet-stream");
header(sprintf('Content-Disposition: attachment; filename="%s"',
$filename ? rcube_imap::decode_mime_string($filename) : "roundcube.$ctype_secondary"));
}
else
{
header("Content-Type: $mimetype");
header(sprintf('Content-Disposition: inline; filename="%s"', rcube_imap::decode_mime_string($filename)));
}
// We need to set the following headers to make downloads work using IE in HTTPS mode.
if (isset($_SERVER['HTTPS']))
{
header('Pragma: ');
header('Cache-Control: ');
}
// deliver part content
if ($ctype_primary=='text' && $ctype_secondary=='html')
{
// we have to analyze the whole structure again to find inline objects
- list($MESSAGE['parts']) = rcmail_parse_message($MESSAGE['structure'],
- array('safe' => (bool)$_GET['_safe'],
- 'prefer_html' => TRUE,
- 'get_url' => $GET_URL.'&_part=%s'));
- $part = &$MESSAGE['parts'][0];
+ list($new_parts, $new_attachments) =
+ rcmail_parse_message($MESSAGE['structure'],
+ array('safe' => (bool)$_GET['_safe'],
+ 'prefer_html' => TRUE,
+ 'get_url' => $GET_URL.'&_part=%s'));
+
+ $all_parts = array_merge($new_parts, $new_attachments);
+ for ($partix = 0; $partix < sizeof($all_parts); $partix++)
+ if ($all_parts[$partix]->mime_id == $pid)
+ $part = &$all_parts[$partix];
// get part body if not available
if (!$part->body)
- $part->body = $IMAP->get_message_part($MESSAGE['UID'], $part->mime_id, $part);
+ $part->body = $IMAP->get_message_part($MESSAGE['UID'], $part->mime_id, $part);
$OUTPUT = new rcube_html_page();
$OUTPUT->write(rcmail_print_body($part, (bool)$_GET['_safe']));
}
else
{
// turn off output buffering and print part content
- //@ob_end_clean();
$IMAP->get_message_part($MESSAGE['UID'], $part->mime_id, $part->encoding, true);
}
exit;
}
}
// print message
else
{
$ctype_primary = strtolower($MESSAGE['structure']->ctype_primary);
$ctype_secondary = strtolower($MESSAGE['structure']->ctype_secondary);
$mimetype = sprintf('%s/%s', $ctype_primary, $ctype_secondary);
// send correct headers for content type
header("Content-Type: text/html");
$cont = '';
list($MESSAGE['parts']) = rcmail_parse_message($MESSAGE['structure'],
array('safe' => (bool)$_GET['_safe'],
'get_url' => $GET_URL.'&_part=%s'));
$cont = "<html>\n<head><title></title>\n</head>\n<body>";
$cont .= rcmail_message_body(array());
$cont .= "\n</body>\n</html>";
$OUTPUT = new rcube_html_page();
$OUTPUT->write($cont);
exit;
}
// if we arrive here, the requested part was not found
header('HTTP/1.1 404 Not Found');
exit;
?>
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 19, 6:27 AM (16 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
458473
Default Alt Text
(149 KB)

Event Timeline