Page MenuHomePhorge

No OneTemporary

Size
360 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/CHANGELOG b/CHANGELOG
index 3601908c3..9202c837a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,1883 +1,1885 @@
CHANGELOG Roundcube Webmail
===========================
- Support image operations with Imagick extension (#1489734)
- Support upload progress with session.upload_progress and PECL uploadprogress module (#1488702)
- Make identity name field optional (#1489510)
- Utility script to remove user records from the local database
- Plugin API: Added message_saved hook (#1489752)
- Support messages import from zip archives
- Zipdownload: Added mbox format support (#1486069)
- Drop support for IE6, move IE7/IE8 support to legacy_browser plugin
- Update to jQuery-2.1.1
- Search across multiple folders (#1485234)
- Improve UI integration of ACL settings
- Drop support for PHP < 5.3.7
- Set In-Reply-To and References for forwarded messages (#1489593)
- Removed redundant default_folders config option (#1489737)
- Implemented IMAP SPECIAL-USE extension support [RFC6154] (#1487830)
+- Add configurable LDAP_OPT_DEREF option (#1489864)
+- Optimize some framed pages content for better performance (#1489792)
- Fix mbox files import
RELEASE 1.0.1
-------------
- Support 'error' and 'body_file' return attribs in 'message_before_send' hook (#1489595)
- Apply user-specific replacements to group's base_dn property (#1489779)
- Fix missing email address when importing contacts from outlook csv (#1489830)
- Fix bug where "With attachment" option in search filter wasn't selected after return from mail view (#1489774)
- Fix "washing" of unicoded style attributes (#1489777)
- Fix unintentional redirect from compose page in Webkit browsers (#1489789)
- Fix messages index cache update under some conditions (e.g. proxy) (#1489756)
- Fix lack of translation of special folders in some configurations (#1489799)
- Fix XSS issue in plain text spellchecker (#1489806)
- Fix invalid page title for some folders (1489804)
- Fix redundant alert message on over-size uploads (#1489817)
- Fix next message display after removing a message (#1489800)
- Fix missing Mail-Followup-To header in sent mail (#1489829)
- Fix error when spell-checking an empty text (#1489831)
- Avoid popupmenus being closed when scrollbar is clicked (#1489832)
- Add proxy_whitelist configuration option (#1489729)
- Fix identities_level=4 handling in new_user_dialog plugin (#1489840)
- Fix various db_prefix issues (#1489839)
- Fix too small length of users.preferences column data type on MySQL
- Fix redundant warning when switching from html to text in empty editor (#1489819)
- Fix invalid host validation on login (#1489841)
- Fix IMAP connection test in installer so it is aware of imap_auth_type (#1489746)
RELEASE 1.0.0
-------------
- Added toolbar button to move message in message view
- Fix style of disabled protocol handler link on IE (#1489569)
- Fix message import dialog when no file is selected (#1489685)
- Fix opening compose screen in new window after saving as draft (#1489643)
- Fix directories check in Installer on Windows (#1489576)
- Fix issue when default_addressbook option is set to integer value (#1489407)
- Fix Opera > 15 detection (#1489562)
- Fix security issue in DomainFactory driver of Password plugin
- Fix invalid X-Draft-Info on forwarded message draft (#1489587)
- Fix regression in handling of 'attachments' result in message_compose hook (#1489627)
- Fix issue where msgexport.sh printed the message to STDOUT instead of a file (#1489634)
- Fix fatal error in database_attachments plugin under some conditions (#1489726)
RELEASE 1.0-rc
--------------
- Small CSS fix with message notice boxes in Larry skin (#1489497)
- Include groups in contacts search on mail compose (#1489082)
- Add mime-type mapping for .7z files (#1489512)
- Invoke update scripts with php to circumvent execution restrictions (#1489322)
- Fix drag & drop message/contact moving on touch device (#1489431)
- Fix canned responses in HTML mode (#1489536)
- Check/create default folders on every login not only the first (#1489423)
- Update to jQuery-1.11.0 and jQuery-UI-1.9.2
- Support SMTP socket context options via new config option 'smtp_conn_options'
- Fix compatibility with PHP 5.2 in html.php file (#1489514)
- Remove expand/collapse with plus/minus keys (on numeric keypad) (#1489513)
- Fix issue where filesystem path was added to all-attachments (zip) file (#1489507)
- Fix case-sensitivity of email addresses handling on compose (#1485499)
- Don't alter Message-ID of a draft when sending (#1489409)
- Fix issue where deprecated syntax for HTML lists was not handled properly (#1488768)
- Display different icons when Trash folder is empty or full (#1485775)
- Remember last position of more headers switch (#1488323)
- Fix so message flags modified by another client are applied on the list on refresh (#1485186)
- Fix broken text/* attachments when forwarding/editing a message (#1489426)
- Improved minified files handling, added css minification (#1486988)
- Fix handling of X-Forwarded-For header with multiple addresses (#1489481)
- Fix border issue on folders list in classic skin (#1489473)
- Implemented menu actions to copy/move messages, added folder-selector widget (#1484086)
- Fix security rules in .htaccess preventing access to base URL without the ending slash (#1489477)
- Fix regression where only first new folder was placed in correct place on the list (#1489472)
- Fix issue where children of selected and collapsed thread were skipped on various actions (#1489457)
- Fix issue where groups were not deleted when "Replace entire addressbook" option on contacts import was used (#1489420)
- Fix unreliable mimetype tests in Installer (#1489453)
- Fix performance of listing writeable folders (#1489451)
RELEASE 1.0-beta
----------------
- Fix handling of invalid closing tags in HTML messages (#1489446)
- Set real content-type for file downloads (#1489439)
- Update TinyMCE to version 3.5.10 (#1489442)
- Fix keyboard navigation in list widgets (#1489392)
- Allow plugins to grab the reference of opened windows (#1489413)
- Larry skin: Improved status message display for better visibility (#1488974)
- Fix Internet Explorer 11 detection (#1489434)
- Fix date column width to fit the widest possible date format (#1489368)
- Move certain user preference options to a collapsed "advanced" block (#1488829)
- Add file type icons for Powerpoint and Open Office presentations (#1489225)
- Fix operations on folders with trailing spaces in name (#1489419)
- Improve identity selection based on From: header (#1489378)
- Fix issue where mails with inline images of the same name contained only the first image multiple times (#1489406)
- Use left/right arrow keys to collapse/expand thread and spacebar to select a row, change Ctrl key behavior (#1489392)
- Fix an issue where using arrow keys to go up a list can result in selected message being under headers (#1489403)
- Fix an issue where Home/End keys don't focus list row properly, don't scrollTo properly (#1489396)
- Add an option to disable smart Reply-List behaviour - reply_all_mode (#1488734)
- Fix an issue where pressing minus key on contacts list was hiding list records (#1489393)
- Fix an issue where shift + arrow-up key wasn't selecting all messages in collapsed thread (#1489397)
- Added icon for priority column in messages list header (#1489234)
- New feature "Canned Responses" to save and recall boilerplate text snippets
- Fix HTML part detection when encapsulated inside multipart/signed (#1489372)
- Add spellchecker backend for the After the Deadline service
- Replace markdown-style [1] link indexes in plain text email bodies
- Improved mailto: link arguments handling (#1489363)
- Use DOMDocument LIBXML_PARSEHUGE and LIBXML_COMPACT options if possible (#1489302)
- Support HTTP_HOST, SERVER_NAME and SERVER_ADDR values in include_host_config feature
- Make default font size for HTML messages configurable (request #118)
- Fix XSS issue in addressbook group name field [CVE-2013-5646] (#1489333)
- After message is sent refresh messages list of replied message folder (#1489249)
- Add option force specified domain in user login - username_domain_forced (#1489264)
- Add option to import Vcards with group assignments
- Save groups membership in Vcard export (#1488509)
- Workaround broken PHP function timezone_name_from_abbr (#1489261)
- Make cached message size limit configurable - messages_cache_threshold (#1489317)
- Log also failed logins to userlogins log
- Add temp_dir_ttl configuration option (#1489304)
- Allow setting INBOX as Sent folder (#1489219)
- Fix replacement variables in user-specific base_dn in some LDAP requests (#1489279)
- Fix image scaling issues when image has only one dimension smaller than the limit (#1489274)
- Fix issue where uploaded photo was lost when contact form did not validate (#1489274)
- Move identity selection based on non-standard headers into (new) identity_select plugin (#1488553)
- Fix downloading binary files with (wrong) text/* content-type (#1489267)
- Respect HTTP_X_FORWARDED_FOR and HTTP_X_REAL_IP variables for session IP check
- Simplified configuration by merging it into one file + defaults (#1487311)
- Make message list header stay on top when scrolling (#1295420)
- Add support for 'enchant' spellcheck engine
- Check filetype detection in installer and update script (#1489193)
- Fix folder names truncation in Classic skin (#1489220)
- Make possible to disable some (broken) IMAP extensions with imap_disable_caps option (#1489184)
- Contacts drag-n-drop default action is to move contacts (#1488751)
- Added possibility to choose to move or copy contacts from drag-n-drop menu (#1488751)
- Fix Close link and remove About link on error pages (#1489109)
- Improved/unified attachment preview screen, added print button
- Fix lack of space between searchfiler and quicksearchbar in Larry skin (#1489158)
- Cache LDAP's user_specific search and use vlv for better performance (#1489186)
- LDAP: auto-detect and use VLV indices for all search operations
- LDAP: additional group configuration options for address books
- LDAP: separated address book implementation from a generic LDAP wrapper class
- Allow address books to browse a multi-level group hierarchy in the contacts list
- Fix session issues when local and database time differs (#1486132)
- Fix thread cache syncronization/validation (#1489028)
- Added feature to import messages to the currently selected folder
- Add option show_real_foldernames to disable localization of special folders
- Fix database cache expunge issues (#1489149)
- Fix date format issues on MS SQL Server (#1488918)
- Add imap_cache_ttl option to configure TTL of imap_cache
- Make LDAP cache engine configurable via ldap_cache and ldap_cache_ttl options
- Fix "duplicate entry" errors on inserts to imap cache tables (#1489146)
- Improved handling of Reply-To/Bcc addresses of identity in compose form (#1489016)
- Added user preference to open all popups as standard windows
- Implemented shared cache (rcube_cache_shared)
- Change Reply-All button label/title when mailing list is detected (#1488938)
- Fix SMTP connection using IPv6 address in smtp_server option (#1489024)
- Added attachment_reminder plugin
- Make PHP code eval() free, use create_function()
- Add option to display email address together with a name in mail preview (#1488732)
- Support CSV import from Atmail (#1489045)
- Add db_prefix configuration option in place of db_table_*/db_sequence_* options
- Make possible to use db_prefix for schema initialization in Installer (#1489067)
- Fix updatedb.sh script so it recognizes also table prefix for external DDL files
- Fix parsing invalid date string (#1489035)
- Add "with attachment" option to messages list filter (#1485382)
- Call resize handler in intervals to prevent lags and double onresize calls in Chrome (#1489005)
- Add rel="noreferrer" for links in displayed messages (#1484686)
- Add ability to toggle between HTML and text while viewing a message (#1486939)
- Remove "HTML message" from attachments list while viewing a message in text mode (#1486939)
- Support IMAP MOVE extension [RFC 6851]
- Add attachment menu with Open and Download options (#1488975)
- Display user-friendly message on IMAP "over quota" errors (#1484164)
- Extended archive plugin with user-configurable options to store messages into subfolders
- Fix export of selected contacts from search result (#1488905)
- Feature to export only selected contacts from addressbook (by Phil Weir)
RELEASE 0.9.5
-------------
- Fix failing vCard import when email address field contains spaces (#1489386)
- Fix default spell-check configuration after Google suspended their spell service
- Fix vulnerability in handling _session argument of utils/save-prefs [CVE-2013-6172] (#1489382)
- Fix iframe onload for upload errors handling (#1489379)
- Fix address matching in Return-Path header on identity selection (#1489374)
- Fix text wrapping issue with long unwrappable lines (#1489371)
- Fixed issues where HTML comments inside style tag would hang Internet Explorer
- Hide Delivery Status Notification option when smtp_server is unset (#1489336)
- Display full attachment name using title attribute when name is too long to display (#1489320)
- Fix attachment icon issue when rare font/language is used (#1489326)
- Fix expanded thread root message styling after refreshing messages list (#1489327)
- Fix issue where From address was removed from Cc and Bcc fields when editing a draft (#1489319)
- Fix error_reporting directive check (#1489323)
- Fix de_DE localization of "About" label in Help plugin (#1489325)
RELEASE 0.9.4
-------------
- Make identities matching case insensitive (#1485480)
- Fix issue where too big message data was stored in cache causing sql errors (#1489316)
- Fix iframe scrollbars on webkit desktop browsers (#1489306)
- Fix issue where legacy config was overriden by default config (#1489288)
- Fix newmail_notifier issue where favicon wasn't changed back to default (#1489313)
- Fix setting of Junk and NonJunk flags by markasjunk plugin (#1489285)
- Fix lack of Reply-To address in header of forwarded message body (#1489298)
- Fix bugs when invoking contact creation form when read-only addressbook is selected (#1489296)
- Fix identity selection on reply (#1489291)
- Fix so additional headers are added to all messages sent (#1489284)
- Fix display issue after moving folder in Folder Manager (#1489293)
- Fix handling of non-default date formats (#1489294)
- Fix unquoted path in PREG expression on Windows (#1489290)
- Fix wrong close tag in /template/mail.html (#1489295)
RELEASE 0.9.3
-------------
- Fix setting refresh_interval to "Never" in Preferences (#1489286)
- Fixed iframe scrolling on touch devices
- Optimized message list for touch devices
- Fix purge action in folder manager (#1489280)
- Fix base URL resolving on attribute values with no quotes (#1489275)
- Fix wrong handling of links with '|' character (#1489276)
- Fix colorspace issue on image conversion using ImageMagick (#1489270)
- Fix XSS vulnerability when editing a message "as new" or draft [CVE-2013-5645] (#1489251)
- Fix XSS vulnerability when saving HTML signatures [CVE-2013-5645] (#1489251)
- Fix rewrite rule in .htaccess (#1489240)
- Fix detecting Turkish language in ISO-8859-9 encoding (#1489252)
- Fix identity-selection using Return-Path headers (#1489241)
- Fix parsing of links with ... in URL (#1489192)
- Fix compose priority selector when opening in new window (#1489257)
- Fix bug where signature wasn't changed on identity selection when editing a draft (#1489229)
- Fix IMAP SETMETADATA parameters quoting (#1489231)
- Fix "could not load message" error on valid empty message body (#1489228)
- Fix handling of message/rfc822 attachments on message forward and edit (#1489214)
- Fix parsing of square bracket characters in IMAP response strings (#1489223)
- Don't clear References and in-Reply-To when a message is "edited as new" (#1489216)
- Fix messages list sorting with THREAD=REFS
- Remove deprecated (in PHP 5.5) PREG /e modifier usage (#1489174)
- Fix empty messages list when register_globals is enabled (#1489157)
- Fix so valid and set date.timezone is not required by installer checks (#1489180)
- Canonize boolean ini_get() results (#1489189)
- Fix so install do not fail when one of DB driver checks fails but other drivers exist (#1489178)
- Fix so exported vCard specifies encoding in v3-compatible format (#1489183)
RELEASE 0.9.2
-------------
- Fix image thumbnails display in print mode (#1489134)
- Fix height of message headers block (#1489108)
- Fix timeout issue on drag&drop uploads (#1489170)
- Fix default sorting of threaded list when THREAD=REFS isn't supported
- Fix list mode switch to 'List' after saving list settings in Larry skin (#1489164)
- Fix error when there's no writeable addressbook source (#1489162)
- Fix zipdownload plugin issue with filenames charset (#1489156)
- Fix so non-inline images aren't skipped on forward (#1489150)
- Fix "null" instead of empty string on messages list in IE10 (#1489145)
- Fix legacy options handling
- Fix so bounces addresses in Sender headers are skipped on Reply-All (#1489011)
- Fix bug where serialized strings were truncated in PDO::quote() (#1489142)
- Fix displaying messages with invalid self-closing HTML tags (#1489137)
- Fix PHP warning when responding to a message with many Return-Path headers (#1489136)
- Fix unintentional compose window resize (#1489114)
- Fix performance regression in text wrapping function (#1489133)
- Fix connection to posgtres db using unix socket (#1489132)
- Fix handling of comma when adding contact from contacts widget (#1489107)
- Fix bug where a message was opened in both preview pane and new window on double-click (#1489122)
- Fix fatal error when xdebug.max_nesting_level was exceeded in rcube_washtml (#1489110)
- Fix PHP warning in html_table::set_row_attribs() in PHP 5.4 (#1489094)
- Fix invalid option selected in default_font selector when font is unset (#1489112)
- Fix displaying contact with ID divisible by 100 in sql addressbook (#1489121)
- Fix browser warnings on PDF plugin detection (#1489118)
- Fix fatal error when parsing UUencoded messages (#1489119)
RELEASE 0.9.1
-------------
- Better German labels for from/to to avoid conflicts with 'sender' (#1489084)
- Fix problem where security warning was displayed for valid images with image/jpg type (#1489097)
- Fix handling of invalid email addresses in headers (#1489092)
- Fix IMAP connection issue with default_socket_timeout < 0 and imap_timeout < 0 (#1489090)
- Fix various PHP code bugs found using static analysis (#1489086)
- Fix backslash character handling on vCard import (#1489085)
- Fix csv import from Thunderbird with French localization (#1489059)
- Fix messages list focus issue in Opera and Webkit (#1489058)
- Fix Reply-To header handling in Reply-All action (#1489037)
- Fix so Sender: address is added to Cc: field on reply to all (#1489011)
- Fix so addressbook_search_mode works also for group search (#1489079)
- Fix removal of a contact from a group in LDAP addressbook (#1489081)
- Inlcude SQL query in the log on SQL error (#1489064)
- Fix handling untagged responses in IMAP FETCH - "could not load message" error (#1489074)
- Fix very small window size in Chrome (#1488931)
- Fix list page reset when viewing a message in Larry skin (#1489076)
- Fix min_refresh_interval handling on preferences save (#1489073)
- Fix PDF support detection for Firefox PDF.js (#1488972)
- Fix possible collision in generated thumbnail cache key (#1489069)
- Fix exit code on bootsrap errors in CLI mode (#1489044)
- Fix error handling in CLI mode, use STDERR and non-empty exit code (#1489043)
- Fix error when using check_referer=true
- Fix incorrect handling of some specific links (#1489060)
- Fix incorrect handling of leading spaces in text wrapping
- Fix unintentional messages list jumps on click in Internet Explorer (#1489056)
- Fix list of required configuration options (#1489055)
- Fix DB error when creating a new contact and a group is selected (#1489051)
- Fix handling of deprecated boolean value of reply_mode option (#1489052)
RELEASE 0.9.0
-------------
- Fix display of HTML entities in protected folder name (#1489042)
- Set minimal permissions to temp files (#1488996)
- Improve content check for embedded images without filename (#1489029)
- Fix handling of invalid characters in message headers and output (#1489032)
- Fix selecting collapsed rows on select-all (#1489036)
- Avoid race-conditions with concurrent attachment uploads (#1488422)
- Fix possible header duplicates when using additional headers (#1489033)
- Fix session issues with use_https=true (#1488986)
- Fix blockquote width in sent mail (#1489031)
- Fix keyboard events on list widgets in Internet Explorer (#1489025)
RELEASE 0.9-rc2
---------------
- Fix security issue in save-pref command
- Remove sig_above configuration option, use reply_mode only (#1489001)
- Refresh current folder in opener window after draft save or message sent (#1488997)
- Fix saving draft just after entering compose window (#1489012)
- Fix javascript error in IE9 when loading form with placeholders into an iframe (#1489008)
- Fix handling of some conditional comment tags in HTML message (#1489004)
- Fix so forward as attachment works if additional attachment is added by message_compose hook (#1489000)
- Better handling of session errors in ajax requests (#1488960)
- Fix HTML part detection for some specific message structures (#1488992)
- Don't show fake address - phishing prevention (#1488981)
- Fix forward as attachment bug with editormode != 1 (#1488991)
- Fix LIMIT/OFFSET queries handling on MS SQL Server (#1488984)
- Fix so task name can really contain all from a-z0-9_- characters (#1488941)
- Fix javascript errors when working in a page opened with taget="_blank"
- Mention SQLite database format change in UPGRADING file (#1488983)
- Increase maxlength to 254 chars for email input fields in addressbook (#1488987)
- Fix thumbnail size when GD extension is used for image resize (#1488985)
- Display notice that message is encrypted also for application/pkcs7-mime messages (#1488526)
RELEASE 0.9-rc
--------------
- Fix plain text spellchecker incorrect highlighting in non-ASCII text (#1488973)
- Add workaround for invalid message charset detection by IMAP servers (#1488968)
- Fix NUL characters in content-type of ms-tnef attachment (#1488964)
- Fix regression in handling LDAP contact identifiers (#1488959)
- Updated translations from Transifex
- Fix buggy error template in a frame (#1488938)
- Add addressbook widget on compose page in classic skin
- Add search box to compose address book widget (#1488381)
- Fix login in case when default_host is an array with one element (#1488928)
- Use LDAP fallback hosts on connect + bind instead of ldap_connect() only.
- Add config option for LDAP bind timeout (sets LDAP_OPT_NETWORK_TIMEOUT option)
- Submit Addressbook advanced search form with Enter key (#1488568)
- Also block remote images in HTML part view (#1488827)
- Improved database schema upgrade procedure, added updatedb.sh script
- Force autocommit mode in mysql database driver (#1488902)
RELEASE 0.9-beta
----------------
- Fix searching by date in address book (#1488888)
- Improve charset detection by prioritizing charset according to user language (#1485669)
- Fix handling of escaped separator in vCard file (#1488896)
- Add option to use envelope From address for MDN responses (#1488880)
- Add possibility to search in message body only (#1488770)
- Support "multipart/relative" as an alias for "multipart/related" type (#1488886)
- Display PGP/MIME signature attachments as "Digital Signature" (#1488570)
- Workaround UW-IMAP bug where hierarchy separator is added to the shared folder name (#1488879)
- Fix version comparisons with -stable suffix (#1488876)
- Add unsupported alternative parts to attachments list (#1488870)
- Add Compose button on message view page (#1488747)
- Display 'Sender' header in message preview
- Plugin API: Added message_before_send hook
- Fix contact copy/add-to-group operations on search result (#1488862)
- Use matching identity in MDN response (#1488864)
- Fix handling of signatures on draft edit (#1488798)
- Fix so compacting of non-empty folder is possible also when messages list is empty (#1488858)
- Allow forwarding of multiple emails (#1486854)
- Fix big memory consumption of DB layer (#1488856)
- Fix broken message/part bodies when FETCH response contains more untagged lines (#1488836)
- Fix empty email on identities list after identity update (#1488834)
- Add new identities_level: (4) one identity with possibility to edit only signature
- Use Delivered-To and Envelope-To headers for identity selection (#1488840, #1488553)
- Fix XSS vulnerability using Flash files (#1488828)
- Always save drafts with format=flowed in order to keep original line wraps (#1488799)
- Select default_addressbook on the list in Address Book (#1488280)
- Fix so mobile phone has TYPE=CELL in exported vCard (#1488812)
- Support contacts import from CSV file (#1486399)
- Improved keep-alive action. Now the interval is based on session_lifetime (#1488507)
- Added cross-task 'refresh' request for system state updates (#1488507)
- Renamed config options: keep_alive to refresh_interval, min_keep_alive to min_refresh_interval
- Fix handling of text/enriched content on message reply/forward/edit
- Option to display attached images as thumbnails below message body
- Upgraded to jQuery 1.8.3 and jQuery UI 1.9.1
- Add config option to automatically generate LDAP attributes for new entries
- Add user settings to open message view and compose form in new windows (#1485486)
- Better client-side timezone detection using the jsTimezoneDetect library (#1488725)
- Add option to disable saving sent mail in Sent folder - no_save_sent_messages (#1488686)
- Fix handling dont_override with message_sort_col and message_sort_order settings (#1488760)
- Fix handling of URLs with asterisk characters (#1488759)
- Remove automatic to-lowercase conversion of usernames (#1488715)
- Plugin API: Add 'email_list' argument for identities data in user_create hook
- Integrated zipdownload plugin to download all attachments (#1445509)
- Fix HTML special characters handling in message list/header display (#1488523)
- List related text/html part as attachment in plain text mode (#1488677)
- Use IMAP BINARY (RFC3516) extension to fetch message/part bodies
- Fix folder creation under public namespace root (#1488665)
- Fix so "Edit as new" on draft creates a new message (#1488687)
- Fix invalid error message on deleting mail from read only folder (#1488694)
- Replace data URIs of images (pasted in HTML editor) with inline attachments (#1488502)
- Remove (too big) min-width on mail screen
- Added template object 'frame'
- Add option to enable HTML editor on forwarding (#1488517)
- Add option to not include original message on reply, rename option top_posting to reply_mode (#1485149)
- Added session_path config option and unified cookies settings in javascript
- Added "Undeleted" option to messages list filter
- Rewritten test scripts for PHPUnit
- Add new DB abstraction layer based on PHP PDO, supporting SQLite3 (#1488332)
- Removed PEAR::MDB2 package
- Removed users.alias column, added option ('user_aliases')
to use email address from identities as username (#1488581)
- Removed redundant cache.cache_id column (#1488528)
- Fix order of attachments in sent mail (#1488423)
- Fix Shift + delete button does not permanently delete messages (#1488243)
- Add Content-Length for attachments where possible (#1485478)
- Fix attachment sizes in message print page and attachment preview page (#1488515)
- Add mail attachments using drag & drop on HTML5 enabled browsers
- Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1485585)
- Display Tiff as Jpeg in browsers without Tiff support (#1488452)
- Don't display Pdf/Tiff/Flash attachments inline without browser support (#1488452, #1487929)
- Add is_escaped attribute for html_select and html_textarea (#1488485)
- Fix issue where draft auto-save wasn't executed after some inactivity time
- Add vCard import from multiple files at once (#1488015)
- Roundcube Framework:
Add possibility to replace IMAP driver with custom class
Add IMAP auto-connection feature, improving performance with caching enabled
Replace imap_init hook with storage_init (with additional 'driver' argument)
Improved performance by caching IMAP server's capabilities in session
Unified global functions naming (rcube_ prefix)
Better classes separation
Framework files moved to lib/Roundcube
RELEASE 0.8.5
-------------
- Fix #countcontrols issue in IE<=8 when text is very long (#1488890)
- Fix unwanted horizontal scrollbar in message preview header (#1488866)
- Add workaround for IE<=8 bug where Content-Disposition:inline was ignored (#1488844)
- Fix XSS vulnerability in vbscript: and data:text links handling [CVE-2012-6121] (#1488850)
- Fix absolute positioning in HTML messages (#1488819)
- Fix cache (in)validation after setting \Deleted flag
- Fix keybord events on messages list in opera browser (#1488823)
- Fix selection of collapsed thread rows (#1488772)
- Fix wrapping of quoted text with format=flowed (#1488177)
RELEASE 0.8.4
-------------
- Fix regression where unintentional page reload was done after request abort (#1488802)
- Fix XSS vulnerability in handling of text/enriched messages (#1488806)
- Fix handling of 'media' attribute on linked css (#1488789)
- Fix excessive LFs at the end of composed message with top_posting=true (#1488797)
- Fix bug where leading blanks were stripped from quoted lines (#1488795)
RELEASE 0.8.3
-------------
- Fix AREA links handling (#1488792)
- Fix possible HTTP DoS on error in keep-alive requests (#1488782)
- Fix compatybility with MDB2 2.5.0b4 (#1488779)
- Fix a bug where saving a message in INBOX wasn't possible
- Fix HTML part detection in messages with attachments (#1488769)
- Fix bug where wrong words were highlighted on spell-before-send check
- Fix scrolling quirk in email preview frame using Opera 12 (#1488763)
- Fix displaying of multipart/alternative messages with empty parts (#1488750)
- Fix threaded list sorting on PHP < 5.2.9 (#1488748)
- Fix Warning: htmlspecialchars(): charset `RCMAIL_CHARSET' not supported warning in Installer (#1488744)
RELEASE 0.8.2
-------------
- Fix XSS vulnerability from HTTP User-Agent header (#1488737)
- Force fonts in compose fields to be all the same (#1488690)
- Fix handling vCard entries with TEL;TYPE=CELL (#1488728)
- Fix error where session wasn't updated after folder rename/delete (#1488692)
- Fix PLAIN authentication for some IMAP servers (#1488674)
- Fix encoding vCard file when contains PHOTO;ENCODING=b (#1488683)
- Fix focus issue in IE when selecting message row (#1488620)
- Add full headers view in message preview window (#1488538)
- Fix message display page issues - unified with message preview (#1488590, #1488642)
- Fix displaying all headers when they contain malformed characters (#1488666)
- Fix decoding of HTML messages with UTF-16 charset specified (#1488654)
- Fix quota capability detection so it can be overwritten by a plugin (#1488655)
- Fix identity selection on reply (#1488101)
- Fix Larry's messages list filter in IE (#1488632)
- Fix more IE issues by disabling Compat. mode with X-UA-Compatible meta tag (#1488626)
- Fix setting locales under Solaris - use additional .UTF-8 suffix (#1488628)
- Fix email address validation for addresses with IP address in domain part
- Fix Larry skin issues in IE7 compat. mode (#1488618)
- Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed
RELEASE 0.8.1
-------------
- Fix bug where domain name was converted to lower-case even with login_lc=false (#1488593)
- Fix lower-casing email address on replies (#1488598)
- Fix line separator in exported messages (#1488603)
- Fix XSS issue where plain signatures wasn't secured in HTML mode [CVE-2012-4668] (#1488613)
- Fix XSS issue where href="javascript:" wasn't secured [CVE-2012-3508] (#1488613)
- Fix impossible to create message with empty plain text part (#1488610)
- Fix stripped apostrophes when replying in plain text to HTML message (#1488606)
- Fix inactive Save search option after advanced search (#1488607)
- Fix Remove from group option is active for contact search result (#1488608)
- Disable autocapitalization in login form on iPad/iPhone (#1488609)
- Fix focus on the list when list row is clicked (#1488600)
- Added separate From and To columns apart from smart From/To column (#1486891)
- Fix fallback to Larry skin when configured skin isn't available (#1488591)
- Fix (workaround) delete operations with some versions of memcache (#1488592)
- Fix (disable) request validation for spell and spell_html actions
RELEASE 0.8.0
-------------
- Don't show product version on login screen (can be enabled by config)
- Renamed old default skin to 'classic'. Larry is the new default skin.
- Support connections to memcached socket file (#1488577)
- Enable TinyMCE inlinepopups plugin
- Update to TinyMCE 3.5.6
- Correctly escape localized labels in javascript variable (#1488567)
- Update Net_SMTP/Auth_SASL packages to fix Digest-MD5/Cram-MD5 authentication (#1488571)
- Don't add attachments content into reply/forward/draft message body (#1488557)
- Fix 'no connection' errors on page unloads (#1488547)
- Plugin API: Add 'unauthenticated' hook (#1488138)
- Show explicit error message when provided hostname is invalid (#1488550)
- Fix wrong compose screen elements focus in IE9 (#1488541)
- Fix fatal error when date.timezone isn't set (#1488546)
- Update to TinyMCE 3.5.4.1
- Better icons with distinct shapes for priority columns (#1488377)
- Show dedicated icon for multipart/report messages (#1488524)
- Properly hide text of icon links/buttons (#1488534)
- Fix handling of unitless CSS size values in HTML message (#1488535)
- Fix removing contact photo using LDAP addressbook (#1488420)
- Fix storing X-ANNIVERSARY date in vCard format (#1488527)
- Update to Mail_Mime-1.8.5 (#1488521)
- Fix XSS vulnerability in message subject handling using Larry skin [CVE-2012-3507] (#1488519)
- Fix handling of links with various URI schemes e.g. "skype:" (#1488106)
- Fix handling of links inside PRE elements on html to text conversion
- Fix indexing of links on html to text conversion
- Decode header value in rcube_mime::get() by default (#1488511)
- Fix errors with enabled PHP magic_quotes_sybase option (#1488506)
- Fix SQL query for contacts listing on MS SQL Server (#1488505)
- Fix window.resize handler on IE8 and Opera (#1488453)
- Don't let error message popups cover the login form (#1488500)
- Update to TinyMCE 3.5.2
- Don't show errors when moving contacts into groups they are already in (#1488493)
- Make folders with unread messages in subfolders bold again (#1486793)
- Abbreviate long attachment file names with ellipsis (#1488499)
- Fix html2text conversion of strong|b|a|th|h tags when used in upper case
- Add listcontrols template container in Larry skin (#1488498)
- Fix host autoselection when default_host is an array (#1488495)
- Move messages forwarding mode setting into Preferences
- Fix HTML entities handling in HTML editor (#1488483)
- Fix listing shared folders on Courier IMAP (#1488466)
RELEASE 0.8-rc
--------------
- Added new translations in Belarusian, Interlingua and Malayalam
- Flipped compose options arrow (#1488474)
- Fix handling of large uuencode attachments (#1488473)
- Fix handling of "usemap" attribute (#1488472)
- Fix handling of some HTML tags e.g. IMG (#1488471)
- Use similar language as a fallback for plugin localization (#1488401)
- Fix issue where signature wasn't re-added on draft compose (#1488322)
- Update to TinyMCE 3.5 (#1488459)
- Fixed multi-threaded autocompletion when number of threads > number of sources
- Allow to configure the number of values allowed for each LDAP attribute
- Support for serialized LDAP address values (usually delimited with a $)
- Less restrictive session auth checks, repeat keep-alive requests on failure (#1488449)
- Fix redirect to mail/compose on re-login (#1488226)
- Add IE8 hack for messages list issue (#1487821)
- Fix handling errors on draft auto-save
- Fix importing vCard photo with ENCODING param specified (#1488432)
- Support mutliple name/email pairs for Bcc and Reply-To identity settings (#1488445)
- Set flexible width to login form fields (#1488418)
- Fix re-draw bug on list columns change in IE8 (#1487822)
- Allow mass-removal of addresses from a group (#1487748)
- Fix removing all contacts on import to LDAP addressbook
- Fix so "Back" from compose/show doesn't reset search request (#1488238)
- Add option to delete messages instead of moving to Trash when in Junk folder (#1486686)
- Fix invisible cursor when replying to a html message (#1487073)
- Reset IP stored in session when destroying session data (#1488056)
- Fix bug where memory_limit = -1 wasn't handled properly
- Support LDAP RFC2256's country object class read/write (#1488123)
- Upgraded to jQuery 1.7.2
- Image resize with GD extension (#1488383)
- Fix lack of warning when switching task in compose window (#1488399)
- Fix bug where it wasn't possible to enter ( or & characters in autocomplete fields
- Request all needed fields from address book backends (#1488394)
- Unified (single) spellchecker button
- Scroll long lists on drag&drop (#1485946)
- Copy all skins in installto script (#1488376)
RELEASE 0.8-beta
----------------
- Upgraded to jQuery 1.7.1 (#1488337) and jQuery UI 1.8.18
- Add Russian to the spellchecker languages list (#1488135)
- Remember custom skin selection after logout (#1488355)
- Make sure About tab is always the last tab (#1488257)
- Fix issue with folder creation under INBOX. namespace (#1488349)
- Added mailto: protocol handler registration link in User Preferences (#1486580)
- Handle identity details box with an iframe (#1487020)
- Fix issue where some text from original message was missing on reply (#1488340)
- Fix autoselect_host() for login (#1488297)
- Changed license to GNU GPLv3+ with exceptions for skins & plugins
- Added address book widget on compose screen
- Use proper timezones from PHP's internal timezonedb (#1485592)
- Add separate pagesize setting for mail messages and contacts (#1488269)
- Deprecate $DB, $USER, $IMAP global variables, Use $RCMAIL instead
- Add option to set default font for HTML message (#1484137)
- Fix issues with big memory allocation of IMAP results
- Prevent from memory_limit exceeding when trying to parse big messages bodies (#1487424)
- Add possibility to add SASL mechanisms for SMTP in smtp_connect hook (#1487937)
- Mark (with different color) folders with recent messages (#1486234)
- Added About tab in Settings
- TinyMCE updated to 3.4.6
RELEASE 0.7.2
-------------
- Fix encoding of attachment with comma in name (#1488389)
- Fix handling of % character in IMAP protocol (#1488382)
- Fix duplicate names handling in addressbook searches (#1488375)
- Fix displaying of HTML messages from Disqus (#1488372)
- Disable E_STRICT warnings on PHP 5.4
- Prevent from folder selection on virtual folder collapsing (#1488346)
- Fix automatic unsubscribe of non-existent folders
- Fix double-quotes handling in recipient names
- User configurable setting how to display contact names in list
- Make contacts list sorting configurable for the admin/user
- Fix parse errors in DDL files for MS SQL Server
- Revert SORT=DISPLAY support, removed by mistake (#1488327)
- Add lost translation label in de_DE (#1488315)
- Fix drafts update issues when edited from preview pane (#1488314)
- Fix wrong variable name in rcube_ldap.php (#1488302)
- Make mime type detection based on filename extension to be case-insensitive
- Fix failure on MySQL database upgrade from 0.7 - text column can't have default value (#1488300)
RELEASE 0.7.1
-------------
- Fix bug in handling of base href and inline content (#1488290)
- Fix SQL Error when saving a contact with many email addresses (#1488286)
- Fix strict email address searching if contact has more than one address
- Remove duplicated 'organization' label (#1488287)
- Fix so editor selector is hidden when 'htmleditor' is listed in 'dont_override'
- Fix wrong (long) label usage (#1488283)
- Fix handling of INBOX's subfolders in special folders config (#1488279)
- Add ifModule statement for setting Options -Indexes in .htaccess file (#1488274)
- Fix crashes with eAccelerator (#1488256)
- Fix searching on IMAP servers without CHARSET specifier support (#1488271)
- Fix expanding folders during drag&drop (#1488260)
- Fix wrong postgres sequence name in upgrade from 0.6
- Fix broken CREATE INDEX queries in SQLite DDL files (#1488255)
RELEASE 0.7
-----------
- Make Roundcube render the Email Standards Project Acid Test correctly
- Replace prompt() with jQuery UI dialog (#1485135)
- Fix navigation in messages search results
- Improved handling of some malformed values encoded with quoted-printable (#1488232)
- Add possibility to do LDAP bind before searching for bind DN
- Fix handling of empty <U> tags in HTML messages (#1488225)
- Add content filter for embedded attachments to protect from XSS on IE [CVE-2012-1253] (#1487895)
- Use strpos() instead of strstr() when possible (#1488211)
- Fix handling HTML entities when converting HTML to text (#1488212)
- Fix fit_string_to_size() renders browser and ui unresponsive (#1488207)
- Fix handling of invalid characters in request (#1488124)
- Fix merging some configuration options in update.sh script (#1485864)
- Fix so TEXT key will remove all HEADER keys in IMAP SEARCH (#1488208)
- Fix handling contact photo url with https:// prefix (#1488202)
- Fix possible infinite redirect on attachment preview (#1488199)
- Improved clickjacking protection for browsers which don't support X-Frame-Options headers
- Fixed bug where similar folder names were highlighted wrong (#1487860)
- Fixed bug in handling link with '!' character in it (#1488195)
- Fixed bug where session ID's length was limited to 40 characters (#1488196)
- TinyMCE security issue: removed moxieplayer (embedding flv and mp4 is not supported anymore)
RELEASE 0.7-beta
----------------
- Fix handling of HTML form elements in messages (#1485137)
- Fix regression in setting recipient to self when replying to a Sent message (#1487074)
- Fix listing of folders in hidden namespaces (#1486796)
- Don't consider \Noselect flag when building folders tree (#1488004)
- Fix sorting autocomplete results (#1488084)
- Add option to set session name (#1486433)
- Add option to skip alternative email addresses in autocompletion
- Fix inconsistent behaviour of Compose button in Drafts folder, add Edit button for drafts
- Fix problem with parsing HTML message body with non-unicode characters (#1487813)
- Add option to define matching method for addressbook search (#1486564, #1487907)
- Make email recipients separator configurable
- Fix so folders with \Noinferiors attribute aren't listed in parent selector
- Fix handling of curly brackets in URLs (#1488168)
- Fix handling of dates (birthday/anniversary) in contact data (#1488147)
- Fix error on opening searched LDAP contact (#1488144)
- Fix redundant line break in flowed format (#1488146)
- Fix IDN address validation issue (#1488137)
- Fix JS error when dst_active checkbox doesn't exist (#1488133)
- Autocomplete LDAP records when adding contacts from mail (#1488073)
- Plugin API: added 'ready' hook (#1488063)
- Ignore DSN request when it isn't supported by SMTP server (#1487800)
- Make sure LDAP name fields aren't arrays (#1488108)
- Fixed imap test to non-default port when using ssl (#1488118)
- Force all files to be overwritten when updating (#1488117)
- Fix issue where it wasn't possible to change list view mode in folder manager for INBOX (#1488107)
- Fix namespace handling in special folders settings (#1488112)
- Disable time limit for CLI scripts (#1488109)
- Fix misleading display when chaning editor type (#1488104)
- Add loading indicator on contact delete
- Fix bug where after delete message rows can be added to the list of another folder (#1487752)
- Add notice on autocompletion that not all records were displayed
- Add option 'searchonly' for LDAP address books
- Add Priority filter to the messages list
- Cache synchronization using QRESYNC/CONDSTORE
- Trigger 'new_messages' hook for all checked folders (#1488083)
- Make date/time format user configurable; drop 'date_today' config option
- Fix setting title for truncated subject in IE (#1487128)
- Fix displaying multipart/alternative messages with only one part (#1487938)
- Rewritten messages caching:
Indexes are stored in a separate table, so there's no need to store all messages in a folder
Added threads data caching
Flags are stored separately, so flag change doesn't cause DELETE+INSERT, just UPDATE
- Improved FETCH response handling
- Improvements in response tokenization method
- Use 'From' and 'To' labels instead of 'Sender' and 'Recipient'
- Fix username case-insensitivity issue in MySQL (#1488021)
- Addressbook Saved Searches
- Added spellchecker exceptions dictionary (shared or per-user)
- Added possibility to ignore words containing caps, numbers, symbols (spellcheck_ignore_* options)
- Added 'priority' column on messages list (#1486782)
- Localize forwarded message header (#1488058)
RELEASE 0.6
-----------
- Fix bug where the last identity is used on reply (#1488101)
- Fix locked folder rename option on servers supporting RFC2086 only (#1488089)
- Fix session race conditions when composing new messages
- Fix encoding of LDAP contacts identifiers (#1488079)
- jQuery 1.6.4
- Fix handling of binary attachments encoded with quoted-printable (#1488065)
- Fix text-overflow:ellipsis issues on messages list in FF7 and Webkit (#1488061)
- Fix handling of links with IP address
- Fix compacting folder resets message list filter (#1488076)
RELEASE 0.6-rc
----------------
- Send X-Frame-Options headers to protect from clickjacking (#1487037)
- Fallback to mail_domain in LDAP variable replacements; added 'host' to 'user_create' hook arguments (#1488024)
- Fixed wrong vCard type parameter mobile (#1488067)
- Fixed vCard WORKFAX issue (#1488046)
- Add vCard's Profile URL support (#1488062)
- jQuery 1.6.3
- Fix imap_cache setting to values other than 'db' (#1488060)
- Fix handling of attachments inside message/rfc822 parts (#1488026)
- Make list of mimetypes that open in preview window configurable (#1487625)
- Added plugin hook 'message_part_get' for attachment downloads
- Added unique connection identifier to IMAP debug messages
- Fix image type check for contact photo uploads
RELEASE 0.6-beta
----------------
- Fixed selecting identity on reply/forward (#1487981)
- Add option to hide selected LDAP addressbook on the list
- Add client-side checking of uploaded files size
- Add newlines between organization, department, jobtitle (#1488028)
- Recalculate date when replying to a message and localize the cite header (#1487675)
- Fix handling of email addresses with quoted local part (#1487939)
- Fix EOL character in vCard exports (#1487873)
- Added optional "multithreading" autocomplete feature
- Plugin API: Added 'config_get' hook
- Fixed new_user_identity plugin to work with updated rcube_ldap class (#1487994)
- Plugin API: added folder_delete and folder_rename hooks
- Added possibility to undo last contact delete operation
- Fix sorting of contact groups after group create (#1487747)
- Add optional textual upload progress indicator (#1486039)
- Fix parsing URLs containing commas (#1487970)
- Added vertical splitter for books/groups list in addressbook (#1487923)
- Improved namespace roots handling in folder manager
- Added searching in all addressbook sources
- Added addressbook source selection in contacts import
- Implement LDAPv3 Virtual List View (VLV) for paged results listing
- Use 'address_template' config option when adding a new address block (#1487944)
- Added addressbook advanced search
- Add popup with basic fields selection for addressbook search
- Case-insensitive matching in autocompletion (#1487933)
- Added option to force spellchecking before sending a message (#1485458)
- Fix handling of "<" character in contact data, search fields and folder names (#1487864)
- Fix saving "<" character in identity name and organization fields (#1487864)
- Added option to specify to which address book add new contacts
- Added plugin hook for keep-alive requests
- Store user preferences in session when write-master is not available and session is stored in memcache, write them later
- Improve performence of folder manager operations
- Fix default_port option handling in Installer when config.inc.php file exists (#1487925)
- Removed option focus_on_new_message, added newmail_notifier plugin
- Added general rcube_cache class with Memcache and APC support
- Improved caching performance by skipping writes of unchanged data
- Option enable_caching replaced by imap_cache and messages_cache options
- Fix WORKFAX saving in address book (#1487910)
- Add forward-as-attachment feature
- jQuery-1.6.2 (#1487913, #1487144)
- Improve display name composition when saving contacts (#1487143)
- Fix problems with subfolders of INBOX folder on some IMAP servers (#1487725)
- Fix handling of folders that doesn't belong to any namespace (#1487637)
- Enable multiselection for attachments uploading in capable browsers (#1485969)
- Add possibility to change HTML editor configuration by skin
- Fix a bug where selecting too many contacts would produce too large URI request (#1487892)
- Improve performance by including files with absolute path (#1487849)
- Move folder name truncation to client/skin (#1485412)
- Added plugin hook for request token creation
- Replace LDAP vars in group queries (#1487837)
- Fix vcard folding with uncode characters (#1487868)
- Keep all submitted data if contact form validation fails (#1487865)
- Handle uncode strings in rcube_addressbook::normalize_string() (#1487866)
- Fix handling of debug_level=4 in ajax requests (#1487831)
- Enable TinyMCE's contextmenu (#1487014)
- Allow multiple concurrent compose sessions
- New config option for custom logo
- Allow skins to define/override texts with <roundcube:label />
- Add simple ACL rights/namespace handling in folder manager
- Force IE to send referers (#1487806)
- Better display of vcard import results (#1485457)
- Improved vcard import
- Interactive update script with improved DB schema check
- Fix problem with contactgroupmembers table creation on MySQL 4.x, add index on contact_id column
- Add LDAP SASL bind and proxy authentication (#1486692)
- Replying to a sent message puts the old recipient as the new recipient (#1487074)
- Fulltext search over (almost) all data for contacts
- Extend address book with rich contact information
RELEASE 0.5.4
-------------
- Fix XSS vulnerability in UI messages [CVE-2011-2937] (#1488030)
RELEASE 0.5.3
-------------
- Fix identities "reply-to" and "bcc" fields have a bogus value when left empty (#1487943)
- Fix issue which cases IMAP disconnection when encrypt() method was used (#1487900)
- Fix some CSS issues in Settings for Internet Explorer
- Fixed handling of folder with name "0" in folder selector
- Fix bug where messages were deleted instead moved to trash folder after Shift key was used (#1487902)
- Fix relative URLs handling according to a <base> in HTML (#1487889)
- Fix handling of top-level domains with more than 5 chars or unicode chars (#1487883)
- Fix usage of non-standard HTTP error codes (#1487797)
- Fix PHP warning on mistaken in_array() usage (#1487901)
RELEASE 0.5.2
-------------
- TinyMCE 3.4.2 now compatible with IE9
- PEAR::Net_SMTP 1.5.2, fixed timeout issue (#1487843)
- Fix bug where template name without plugin prefix was used in render_page hook
- Support 'abort' and 'result' response in 'preferences_save' hook, add error handling
- Fix bug where some content would cause hang on html2text conversion (#1487863)
- Improve space-stuffing handling in format=flowed messages (#1487861)
- Fix bug where some dates would produce SQL error in MySQL (#1487856)
- Added workaround for some IMAP server with broken STATUS response (#1487859)
- Fix bug where default_charset was not used for text messages (#1487836)
- Stateless request tokens. No keep-alive necessary on login page (#1487829)
- Force names of unique constraints in PostgreSQL DDL
- Add code for prevention from IMAP connection hangs when server closes socket unexpectedly
- Remove redundant DELETE query (for old session deletion) on login
- Get around unreliable rand() and mt_rand() in session ID generation (#1486281)
- Fix some emails are not shown using Cyrus IMAP (#1487820)
- Fix handling of mime-encoded words with non-integral number of octets in a word (#1487801)
- Fix parsing links with non-printable characters inside (#1487805)
- Fixed de_CH Localization bugs (#1487773)
- Add variable for 'Today' label in date_today option (#1486120)
- Fix dont_override setting does not override existing user preferences (#1487664)
- Use only one from IMAP authentication methods to prevent login delays (1487784)
- Support strftime format in date_today option
- Fix SQL query in rcube_user::query() so it uses index on MySQL again
- Removed redundant </form> tags from contact add/edit pages
- Fix CSS error in contact details screen on IE7 (#1487775)
RELEASE 0.5.1
-------------
- Fix handling of attachments with invalid content type (#1487767)
- Add workaround for DBMail's bug http://www.dbmail.org/mantis/view.php?id=881 (#1487766)
- Use IMAP's ID extension (RFC2971) to print more info into debug log
- Security: add optional referer check to prevent CSRF in GET requests
- Fix email_dns_check setting not used for identities/contacts (#1487740)
- Fix ICANN example addresses doesn't validate (#1487742)
- Security: protect login form submission from CSRF [CVE-2011-1491]
- Security: prevent from relaying malicious requests through modcss.inc [CVE-2011-1492]
- Fix handling of non-image attachments in multipart/related messages (#1487750)
- Fix IDNA support when IDN/INTL modules are in use (#1487742)
- Fix handling of invalid HTML comments in messages (#1487759)
- Fix parsing FETCH response for very long headers (#1487753)
- Fix add/remove columns in message list when message_sort_order isn't set (#1487751)
- Check mime headers before attempt to parse them (#1487745)
- Quote header values in show_additional_headers plugin (#1487744)
- Fix settings UI on IE 6 (#1487724)
- Remove double borders in folder listing (#1487713)
- Separate full message headers UI element from headers table (#1487715)
- Add part MIME ID to message_part_* hooks (#1487718)
- Improve parsing of MS Outlook vCards (#1487716)
- Updated PEAR::Net_Socket to 1.0.10
- Updated PEAR::Net_IDNA2 to 0.1.1
- Fix handling of comments inside an email address spec. (#1487673)
- Show full mail subject as title when hovering a cut subject link (#1487128)
- Fix randomly disappearing folders list in IE (#1487704)
- Fix list column add/removal in IE (#1487703)
- Fix login redirect issues (#1487686)
- Require PHP 5.2.1 or greater
- Fix %h/%z variables in username_domain option (#1487701)
- Workaround for setting charset in case of malformed bodystructure response (#1487700)
- Fix impossible to subscribe to protected folders (#1487656)
- Fix setting timezone in Preferences (#1487705)
RELEASE 0.5
-----------
- Fix double-login/session issue (#1487104)
- Wrap HTML parts with <html><body> and add Doctype declaration (#1487098)
- Make rcube_autoload silently skip unknown classes (#1487109)
- Fix charset detection in vcards with encoded values (#1485542)
- Better CSS cursors for splitters (#1486874)
- Show the same message only once (#1487641)
- Fix namespaces handling (#1487649)
- Add handling of multifolder METADATA/ANNOTATION responses
- Fix handling of INBOX when personal namespace prefix is non-empty (#1487657)
- Fix handling square brackets in links (#1487672)
- Add description of 'use_https' option in main.inc.php.dist file
RELEASE 0.5-RC
--------------
- Plugin API: Add 'pass' argument in 'authenticate' hook (#1487134)
- Fix attachments of type message/rfc822 are not listed on attachments list
- Add 'login_lc' config option for case-insensitive authentication (#1487113)
- Fix window is blur'ed in IE when selecting a message (#1487316)
- Fix cursor position on compose form in Webkit browsers (#1486674)
- Fix setting charset of attachment filenames (#1487122)
- Allow setting autocomplete attribute for all inputs separately (#1487313)
- New Folder Manager UI
- Fix invalid Request when creating a folder (#1487443)
- Add folder size and quota indicator in folder manager (#1485780)
- Add possibility to move a subfolder into root folder (#1486791)
- Fix copying all messages in a folder copies only messages from current page
- Improve performance of moving or copying of all messages in a folder
- Fix plaintext versions of HTML messages don't contain placeholders for emotions (#1485206)
- Improve performance of folder rename and delete actions
- Better support for READ-ONLY and NOPERM responses handling (#1487083)
- Add confirmation message on purge/expunge command response
- Fix handling of untagged responses for AUTHENTICATE command (#1487450)
- Add username and IP address to log message on unsuccessful login (#1487626)
- Improved Mail-Followup-To and Mail-Reply-To headers handling
- Fix charset conversion for text attachments without charset specification (#1487634)
RELEASE 0.5-BETA
----------------
- Make session data storage more robust against garbage session data (#1487136)
- Config option for autocomplete on login screen
- Allow plugin templates to include local files (#1487133)
- List groups in address detail view and allow to subscribe/unsubscribe from there (#1486753)
- Messages caching: performance improvements, fixed syncing, fixes related with #1486748
- Add link to identities in compose window (#1486729)
- Add Internationalized Domain Name (IDNA) support (#1483894)
- Add option to automatically send read notifications for known senders (#1485883)
- Add option to "Return receipt" will be always checked (#1486352)
- Fix HTML to plain text conversion doesn't handle citation blocks (#1486921)
- Use custom sorting when SORT is disabled by IMAP admin (#1486959)
- Allow setting some washtml options from plugin (#1486578)
- Add option do bind for an individual LDAP address book (#1486997)
- Change reply prefix to display email address only if sender name doesn't exist (#1486550)
- Plugin API: improved 'abort' flag handling, added 'result' item in some hooks (#1486914)
- Fix mailto optional params in plain text messages aren't handled (#1487026)
- Add Reply-to-List feature (#1484252)
- Add Mail-Followup-To/Mail-Reply-To support (#1485547)
- Fix confirmation message isn't displayed after sending mail on Chrome (#1486177)
- Fix keyboard doesn't work with autocomplete list with Chrome (#1487029)
- Improve tabs to fixed width and add tabs in identities info (#1486974)
- Add unique index on users.username+users.mail_host
- Make htmleditor option more consistent and add option to use HTML on reply to HTML message (#1485840)
- Use empty envelope sender address for message disposition notifications (RFC 2298.3)
- Support SMTP Delivery Status Notifications - RFC 3461 (#1486142)
- Use css sprite image for messages list
- Add (different) attachment icon for messages of type multipart/report (#1486165)
- Prevent from inserting empty link when composing HTML message (#1486944)
- Add caching support in id2uid and uid2id functions (#1487019)
- Add SASL proxy authentication for SMTP (#1486693)
- Improve displaying of UI messages (#1486977)
- Fix double e-mail filed in identity form (#1487054)
- Display IMAP errors for LIST/THREAD/SEARCH commands (#1486905)
- Add LITERAL+ (IMAP4 non-synchronizing literals) support (RFC 2088)
- Add separate column for message status icon (#1486665)
- Add ACL extension support into IMAP classes (RFC 4314)
- Add ANNOTATEMORE extension support into IMAP classes (draft-daboo-imap-annotatemore)
- Add METADATA extension support into IMAP classes (RFC 5464)
- Fix decoding of e-mail address strings in message headers (#1487068)
- Fix handling of attachments when Content-Disposition is not inline nor attachment (#1487051)
- Improve performance of unseen messages counting (#1487058)
- Improve performance of messages counting using ESEARCH extension (RFC4731)
- Add LIST-STATUS support in rcube_imap_generic class (RFC 5819)
- Add SASL-IR support in IMAP (RFC 4959)
- Add LOGINDISABLED support (RFC 2595)
- Add support for AUTH=PLAIN in IMAP authentication
- Re-implemented SMTP proxy authentication support
- Add support for IMAP proxy authentication (#1486690)
- Add support for AUTH=DIGEST-MD5 in IMAP (RFC 2831)
- Fix parent folder with unread subfolder not bold when message is open (#1487078)
- Add basic IMAP LIST's \Noselect option support
- Add support for selection options from LIST-EXTENDED extension (RFC 5258)
- Don't list subscribed but non-existent folders (#1486225)
- Fix handling of URLs with tilde (~) or semicolon (;) character (#1487087, #1487088)
- Plugin API: added 'contact_form' hook
- Add SORT=DISPLAY support (RFC 5957)
- Plugin API: add possibility to disable plugin in AJAX mode, 'noajax' property
- Plugin API: add possibility to disable plugin in framed mode, 'noframe' property
- Improve performance of setting IMAP flags using .SILENT suffix
- Improve performance of message cache status checking with skip_disabled=true
- Support contact's email addresses up to 255 characters long (#1487095)
- Add option to place replies in the folder of the message being replied to (#1485945)
- Add missing confirmation/error messages on contact/group/message actions (#1486845)
- Add 'loading' message on message move/copy/delete/mark actions
- Improve responsiveness of messages displaying (#1486986)
- Add option for minimum length of autocomplete's string (#1486428)
- Fix operations on messages in unsubscribed folder (#1487107)
- Add support for shared folders (#1403507)
- Fix handling of folders with name "0" (#1487119)
- Fix handling of folders with "<>" characters in name
- jQuery 1.4.4
- Fix handling of HTML entity strings in plain text messages
- Fix focused elements aren't unfocused when clicking on the list (#1487123)
- Fix error in MSSQL DDL scripts (#1487112)
- Lock submit button in onsubmit event on login page (#1487036)
- Don't set attachment's charset in Content-type header (#1487122)
- Fix handling of message bodies (quoted-printable encoded) with NULL characters (#1486189)
- Add workaround for MSOE's multipart/related messages with non-related attachments
RELEASE 0.4.2
-------------
- Fix handling of backslash as IMAP delimiter
- Fix charset replacement in HTML message bodies (#1487021)
- Fix: contact group input is empty when using rename action more than once on the same group record
- Fix "Server Error! (Not Found)" when using utils/save-pref action (#1487023)
- Fix handling of Thunderbird's vCards (#1487024)
RELEASE 0.4.1
-------------
- Fix space-stuffing in format=flowed messages (#1487018)
- Fix msgexport.sh now using the new imap wrapper
- Avoid displaying password on shell (#1486947)
- Only lower-case user name if first login attempt failed (#1486393)
- Make alias setting in squirrelmail_usercopy plugin configurable (patch by pommi, #1487007)
- Prevent from saving a non-existing skin path in user prefs (#1486936)
- Improve handling of single-part messages with bogus BODYSTRUCTURE (#1486898)
- Fix path to SQL files when using pgsql/mysqli/sqlsrv drivers (#1486902)
- Fix upgrade script for SQLite (#1486903)
- Fixes in SQL init script + added update script for MSSQL database
- Remove redundant date in syslog messages (#1486945)
- Fix contacts list page controls when a group is selected (#1486946)
- Fix SMTP test in Installer (#1486952)
- Fix "Select all" causes message to be opened in folder with exactly one message (#1486913)
- Fix Tab key doesn't work in HTML editor in Google Chrome (#1486925)
- Fix TinyMCE uses zh_CN when zh_TW locale is set (#1486929)
- Fix TinyMCE buttons are hidden in Opera (#1486922)
- Fix JS error on IE when trying to send HTML message with enabled spellchecker (#1486940)
- Display inline images with known extensions and non-image content-type (#1486934)
- Fix "Threaded" checkbox after subfolder creation (#1486928)
- Fix timezone string in sent mail (#1486961)
- Show disabled checkboxes for protected folders instead of dots (#1485498)
- Added fieldsets in Identity form, added 'identity_form' hook
- Re-added 'Close' button in upload form (#1486930, #1486823)
- Fix handling of charsets with LATIN-* label
- Fix messages background image handling in some cases (#1486990)
- Fix format=flowed handling (#1486989)
- Fix when IMAP connection fails in 'get' action session shouldn't be destroyed (#1486995)
- Fix list_cols is not updated after column dragging (#1486999)
- Support %z variable in host configuration options (#1487003)
RELEASE 0.4
-----------
- Fix disapearing upload form disapears when user selects a file on Safari (#1486823)
- Don't replace error messages with loading info (#1486300)
- Fix JS errors on compose mode switch (#1486870)
- Fix message structure parsing when it lacks optional fields (#1486881)
- Include all recipients in sendmail log
- Support HTTP_X_FORWARDED_PROTO header for HTTPS detecting (#1486866)
- Fix default IMAP port configuration (#1486864)
- Create Sent folder when starting to compose a new message (#1486802)
- Fix handling of messages with Content-Type: application/* and no filename (#1484050)
- Improved compose screen: resizable body and attachments list, vertical splitter, options menu
- Fix RC forgets search results (#1483883)
- TinyMCE 3.3.7
- Improve parsing of styled empty tags in HTML messages (#1486812)
- Add %dc variable support in base_dn/bind_dn config (#1486779)
- Add button to hide/unhide the preview pane (#1484215)
- Fix no-cache headers on https to prevent content caching by proxies (#1486798)
- Fix attachment filenames broken with TNEF decoder using long filenames (#1486795)
- Use user's timezone in Date header, not server's timezone (#1486119)
- Add option to set separate footer for HTML messages (#1486660)
- Add real SMTP error description to displayed error messages (#1485927)
- Fix some IMAP errors handling when opening the message (#1485443)
- Fix related parts aren't displayed when got mimetype other than image/* (#1486432)
- Multiple identity and database support for squirrelmail_usercopy plugin (#1486517)
- Support dynamic hostname (%d/%n) variables in configuration options (#1485438)
- Add 'messages_list' hook (#1486266)
- Add request* event triggers in http_post/http_request (#1486054)
- Fix use RFC-compliant line-delimiter when saving messages on IMAP (#1486712)
- Add 'imap_timeout' option (#1486760)
- Fix forwarding of messages with winmail attachments
- Fix handling of uuencoded attachments in message body (#1485839)
- Added list_mailboxes hook in rcube_imap::list_unsubscribed() (#1486668)
- Fix wrong message on file upload error (#1486725)
- Add support for data URI scheme [RFC2397] (#1486740)
- Added 'actionbefore', 'actionafter', 'responsebefore', 'responseafter' events
- Fix double-addition of e-mail domain to content ID in HTML images
- Read and send messages with format=flowed (#1484370), fixes word wrapping issues (#1486543)
- Fix duplicated attachments when forwarding a message (#1486487)
- Fix message/rfc822 attachments containing only attachments are not parsed properly (#1486743)
- Fix %00 character in winmail.dat attachments names (#1486738)
- Fix handling errors of folder deletion (#1486705)
- Parse untagged CAPABILITY response for LOGIN command (#1486742)
- Renamed all php-cli scripts to use .sh extension
- Some files from /bin + spellchecking actions moved to the new 'utils' task
- Added thread tree icons
- Extend contact groups support (#1486682)
- Fix check-recent action issues and performance (#1486526)
- Fix messages order after checking for recent (#1484664)
- Fix autocomplete shows entries without email (#1486452)
- Fix listupdate event doesn't trigger on search response (#1486708)
- Fix select_all_mode value after selecting a message (#1486720)
- Set focus to editor on reply in HTML mode (#1486632)
- Fix composing in HTML jumps cursor to body instead of recipients (#1486674)
- Allow columns order change per user - drag&drop (#1485795)
- Add References header in read receipt (#1486681)
- Fix database constraint violation when opening a message (#1486696)
- Add 'loading' message while login is in progress (#1486667)
- Fix quota_zero_as_unlimited (#1486662)
- Fix folder subscription checking (#1486684)
- Fix INBOX appears (sometimes) twice in mailbox list (#1486672)
- Fix listing of attachments of some types e.g. "x-epoc/x-sisx-app" (#1486653)
- Fix DB Schema checking when some db_table_* options are not set (#1486654)
RELEASE 0.4-beta
----------------
- Add sizelimit and timelimit variables in LDAP config (#1486544)
- Hide IMAP host dropdown when single host is defined (#1486326)
- Add images pre-loading on login page (#1451160)
- Add HTTP_X_REAL_IP and HTTP_X_FORWARDED_FOR to successful logins log (#1486441)
- Fix setting spellcheck languages with extended codes (#1486605)
- Fix messages list scrolling in FF3.6 (#1486472)
- Fix quicksearch input focus (#1486637)
- Always set changed date when flagging a DB record as deleted + provide a cleanup script
- Fix address book/group selection (#1486619)
- Assign newly created contacts to the active group (#1486626)
- Added option not to mark messages as read when viewed in preview pane (#1485012)
- Allow plugins modify the Sent folder when composing (#1486548)
- Added optional (max_recipients) support to restrict total number of recipients per message (#1484542)
- Re-organize editor buttons, add blockquote and search buttons
- Make possible to write inside or after a quoted html message (#1485476)
- Fix bugs on unexpected IMAP connection close (#1486190, #1486270)
- Iloha's imap.inc rewritten into rcube_imap_generic class
- Added contact groups in address book (not finished yet)
- Added PageUp/PageDown/Home/End keys support on lists (#1486430)
- Added possibility to select all messages in a folder (#1484756)
- Added 'imap_force_caps' option for after-login CAPABILITY checking (#1485750)
- Password: Support dovecotpw encryption
- TinyMCE 3.3.1
- Implemented messages copying using drag&drop + SHIFT (#1484086)
- Improved performance of folders operations (#1486525)
- Fix blocked.gif attachment is not attached to the message (#1486516)
- Managesieve: import from Horde-INGO
- Managesieve: support for more than one match (#1486078)
- Managesieve: support for selectively disabling rules within a single sieve script (#1485882)
- Threaded message listing now available
- Added sorting by ARRIVAL and CC
- Message list columns configurable by the user
- Removed 'index_sort' option, now we're using empty 'message_sort_col' for this
- virtuser_query: support other identity data (#1486148)
- Options virtuser_* replaced with virtuser_* plugins
- Plugin API: Implemented 'email2user' and 'user2email' hooks
- Fix forwarding message omits CC header (#1486305)
- Add 'default_charset' option to user preferences (#1485451)
- Add 'delete_always' option to user preferences
- Support/Require tls:// prefix in 'smtp_server' option for TLS connections
- Fix inconsistent behaviour of 'delete_always' option (#1486299)
- Fix deleting all messages from last list page (#1486293)
- Flag original messages when sending a draft (#1486203)
- Changed signature separator when top-posting (#1486330)
- Let the admin define defaults for search modifiers (#1485897)
- Fix long e-mail addresses validation (#1486453)
- Remember search modifiers in user prefs (#1486146)
- Added force_7bit option to force MIME encoding of plain/text messages (#1486510)
- Use case sensitive check when checking for default folders (#1486346)
- Fix checking for new mail: now checks unseen count of inbox (#1485794)
- Improve performance by avoiding unnecessary updates to the session table (#1486325)
- Fix invalid <font> tags which cause HTML message rendering problems (#1486521)
- Fix CVE-2010-0464: Disable DNS prefetching (#1486449)
- Fix Received headers to behave better with SpamAssassin (#1486513)
- Password: Make passwords encoding consistent with core, add 'password_charset' global option (#1486473)
- Fix adding contacts SQL error on mysql (#1486459)
- Squirrelmail_usercopy: support reply-to field (#1486506)
- Fix IE spellcheck suggestion popup issue (#1486471)
- Fix email address auto-completion shows regexp pattern (#1486258)
- Fix merging of configuration parameters: user prefs always survive (#1486368)
- Fix quota indicator value after folder purge/expunge (#1486488)
- Fix external mailto links support for use as protocol handler (#1486037)
- Fix attachment excessive memory use, support messages of any size (#1484660)
- Fix setting task name according to auth state
- Password: fix vpopmaild driver (#1486478)
- Add workaround for MySQL bug [http://bugs.mysql.com/bug.php?id=46293] (#1486474)
- Fix quoted text wrapping when replying to an HTML email in plain text (#1484141)
- Fix handling of extended mailto links (with params) (#1486354)
- Fix sorting by date of messages without date header on servers without SORT (#1486286)
- Fix inconsistency when not using default table names (#1486467)
- Fix folder rename/delete buttons do not appear on creation of first folder (#1486468)
- Fix character set conversion fails on systems where iconv doesn't accept //IGNORE (#1486375)
- Log in performance: Create default folders on first login only
- Import contacts into the selected address book (by Phil Weir)
- Add support for MDB2's 'sqlsrv' driver (#1486395)
- Use jQuery-1.4
- Removed problematic browser-caching of messages
- Fix incompatybility with suhosin.executor.disable_emodifier (#1486321)
- Use PLAIN auth when CRAM fails and imap_auth_type='check' (#1486371)
- Fix removal of <title> tag from HTML messages (#1486432)
- Fix 'force_https' to specified port when URL contains a port number (#1486411)
- Fix to-text converting of HTML entities inside b/strong/th/hX tags (#1486422)
- Bug in spellchecker suggestions when server charset != UTF8 (#1486406)
- Managesieve: Fix requires generation for multiple actions (#1486397)
- Fix LDAP problem with special characters in RDN (#1486320)
- Improved handling of message parts of type message/rfc822
- Plugin API: added 'quota' hook
- Fix parsing conditional comments in HTML messages (#1486350)
- Use built-in json_encode() for proper JSON format in AJAX replies
- Allow setting only selected params in 'message_compose' hook (#1486312)
- Plugin API: added 'message_compose_body' hook (#1486285)
- Fix counters of all folders are checked in 'getunread' action with check_all_folders disabled (#1486128)
- Fix displaying alternative parts in messages of type message/rfc822 (#1486246)
- Fix possible messages exposure when using Roundcube behind a proxy (#1486281)
- Fix unicode para and line separators in javascript response (#1486310)
- Additional_message_headers: allow unsetting headers, support plugin's config file (#1486268)
- Fix displaying of hidden directories in skins list (#1486301)
- Fix open_basedir restriction error when reading skins list (#1486304)
- Fix pasting from Office apps into html editor (#1486271)
- Fix empty <a> tags parsing (#1486272)
- Don't cut off attachment names when using non-RFC2231 encoding (#1485515)
- Allow inserting signatures above replied message body (#1484272)
- Managesieve 2.0: multi-script support
- Fix imap_auth_type regression (#1486263)
RELEASE 0.3.1
------------------
- Specify toolbar container in compose template (#1486247)
- Fix $_SERVER['HTTPS'] check for SSL forcing on IIS (#1486243)
- Avoid unnecessary page loads for selected tab (#1486032)
- Fix quota indicator issues by content generation on client-size (#1486197, #1486220)
- Don't display disabled sections in Settings (#1486099)
- Added server-side e-mail address validation with 'email_dns_check' option (#1485857)
- Fix login page loading into an iframe when session expires (#1485952)
- Allow setting port number in 'force_https' option (#1486091)
- Option 'force_https' replaced by 'force_https' plugin
- Fix IE issue with non-UTF-8 characters in AJAX response (#1486159)
- Partially fixed "empty body" issue by showing raw body of malformed message (#1486166)
- Fix importing/sending to email address with whitespace (#1486214)
- Added XIMSS (CommuniGate) driver for Password plugin
- Fix newly attached files are not saved in drafts w/o editing any text (#1486202)
- Added attachment upload indicator with parallel upload (#1486058)
- Use default_charset for bodies of messages without charset definition (#1486187)
- Password: added cPanel driver
- Fix return to first page from e-mail screen (#1486105)
- Fix handling HTML comments in HTML messages (#1486189)
- Fix folder/messagelist controls alignment - icons used (#1486072)
- Fix LDAP addressbook shows 'Contact not found' error sometimes (#1486178)
- Fix cache status checking + improve cache operations performance (#1486104)
- Prevent from setting INBOX as any of special folders (#1486114)
- Fix regular expression for e-mail address (#1486152)
- Fix Received header format
- Implemented sorting by message index - added 'index_sort' option (#1485936)
- Fix dl() use in installer (#1486150)
- Added 'ldap_debug' option
- Fix "Empty startup greeting" bug (#1486085)
- Fix setting user name in 'new_user_identity' plugin (#1486137)
- Fix incorrect count of new messages in folder list when using multiple IMAP clients (#1485995)
- Fix all folders checking for new messages with disabled caching (#1486128)
- Support skins in 'archive' and 'markasjunk' plugins
- Added 'html_editor' hook (#1486068)
- Fix DB constraint violation when populating messages cache (#1486052)
- Password: added password strength options (#1486062)
- Fix LDAP partial result warning (#1485536)
- Fix delete in message view deletes permanently with flag_for_deletion=true (#1486101)
- Use faster/secure mt_rand() (#1486094)
- Fix roundcube hangs on empty inbox with bincimapd (#1486093)
- Fix wrong headers for IE on servers without $_SERVER['HTTPS'] (#1485926)
- Force IE style headers for attachments in non-HTTPS session, 'use_https' option (#1485655)
- Check 'post_max_size' for upload max filesize (#1486089)
- Password Plugin: Fix %d inserts username instead of domain (#1486088)
- Fix rcube_mdb2::affected_rows() (#1486082)
RELEASE 0.3-stable
------------------
- Fix gn and givenName should be synonymous in LDAP addressbook (#1485892)
- Add mail_domain to LDAP email entries without @ sign (#1485201)
- Fix saving empty values in LDAP contact data (#1485781)
- Fix LDAP contact update when RDN field is changed (#1485788)
- Fix LDAP attributes case senitivity problems (#1485830)
- Fix LDAP addressbook browsing when only one directory is used (#1486022)
- Fix endless loop on error response for APPEND command (#1486060)
- Don't require date.timezone setting in installer (#1485989)
- Fix date sorting problem with Courier IMAP server (#1486065)
- Unselect pressed buttons on mouse up (#1485987)
- Don't set php_value error_log in .htaccess but mention in INSTALL (#1485924)
- Fix too small status/flag/attachment columns in Safari 4 (#1486063)
- Fix selection disabling while dragging splitter in webkit browsers (#1486056)
- Added 'new_messages' plugin hook (#1486005)
- Added 'logout_after' plugin hook (#1486042)
- Added 'message_compose' hook
- Added 'imap_connect' hook (#1485956)
- Fix vcard_attachments plugin (#1486035)
- Updated PEAR::Auth_SASL to 1.0.3 version
- Use sequence names only with PostgreSQL (#1486018)
- Re-designed User Preferences interface
- Fix MS SQL DDL (#1486020)
- Fix rcube_mdb2.php: call to setCharset not implemented in mssql driver (#1486019)
- Added 'display_next' option
- Fix rcube_mdb2::unixtimestamp for MS SQL (#1486015)
- Fix HTML washing to respect character encoding
- Fix endless loop in iil_C_Login() with Courier IMAP (#1486010)
- Fix #messagemenu display on IE (#1486006)
- Speedup UI by using sprites for (toolbar) buttons
- Fix charset names with X- prefix handling
- Fix displaying of HTML messages with unknown/malformed tags (#1486003)
RELEASE 0.3-RC1
---------------
- Fix import of vCard entries with params (#1485453)
- Fix HTML messages output with empty block elements (#1485974)
- Use request tokens to protect POST requests from CSRF [CVE-2009-4076, CVE-2009-4077]
- Added hook when killing a session
- Added hook to write_log function (#1485971)
- Performance improvements by use UID commands (#1485690)
- Fix HTML editor tabIndex setting (#1485972)
- Added 'imap_debug' and 'smtp_debug' options
- Support strftime's format modifiers in date_* options (#1484806)
- Support %h variable in 'smtp_server' option (#1485766)
- Show SMTP errors in browser (#1485927)
- Allow WBR tag in HTML message (#1485960)
- Use spl_autoload_register() instead of __autoload (#1485947)
- Add hook for identities listing (#1485958)
- Trigger hook 'smtp_connect' when opening an SMTP connection (#1485954)
- Added config option to enforce HTTPS connections
- Fix non-unicode characters caching in unicode database (#1484608)
- Performance improvements of messages caching
- Fix empty Date header issue (#1485923)
- Open collapsed folders during drag & drop (#1485914)
- Fixed link text replacements (#1485789)
- Also trigger 'insertrow' events on page load (#1485826)
- No link on subject in IE browsers (#1484913)
- Fixed filename encoding according to RFC2231 (#1485875)
- Added message Edit feature (#1483891, #1484440)
- Fix message Etag generation for counter issues (#1485623)
- Fix messages searching on MailEnable IMAP (#1485762)
- Fixed many 'skip_deleted' issues (#1485634)
- Fixed messages list sorting on servers without SORT capability
- Colorized signatures in plain text messages
- Reviewed/fixed skip_deleted/read_when_deleted/flag_for_deletion options handling in UI
- Fix displaying of big maximum upload filesize (#1485889)
- Added possibility to invert messages selection
- After move/delete from 'show' action display next message instead of messages list (#1485887)
- Fixed problem with double quote at the end of folder name (#1485884)
- Speedup UI by using CSS sprites and etags/expires/deflate in Apache config (#1484858,#1485800)
- Support UID EXPUNGE: remove only moved/deleted messages
- Add drag cancelling with ESC key (#1484344)
- Support initial identity name from virtuser_query (#1484003)
- Added message menu, removed Print and Source buttons
- Added possibility to save message as .eml file (#1485861)
- Added 1 minute interval in autosave options (#1485854)
- Support UTF-7 encoding in messages (#1485832)
- Better support for malformed character names (#1485758)
RELEASE 0.3-BETA
----------------
- Plugin API + jQuery engine
- Added possibility to encrypt received header, option 'http_received_header_encrypt',
added some more logic in encrypt/decrypt functions for security
- Fix Answered/Forwarded flag setting for messages in subfolders
- Fix autocomplete problem with capital letters (#1485792)
- Support UUencode content encoding (#1485839)
- Minimize chance of race condition in session handling (#1485659, #1484678)
- Fix session handling on non-session SQL query error (#1485734)
- Fix html editor mode setting when reopening draft message (#1485834)
- Added quick search box menu (#1484304)
- Fix wrong column sort order icons (#1485823)
- Updated TinyMCE to 3.2.3 version
- Fix attachment names encoding when charset isn't specified in attachment part (#1484969)
- Fix message normal priority problem (#1485820)
- Fix autocomplete spinning wheel does not disappear (#1485804)
- Added log_date_format option (#1485709)
- Fix text wrapping in HTML editor after switching from plain text to HTML (#1485521)
- Fix auto-complete function hangs with plus sign (#1485815)
- Fix AJAX requests errors handler (#1485000)
- Speed up message list displaying on IE
- Fix read/write database recognition (#1485811)
RELEASE 0.2.2
-------------
- Fix quicksearchbox look in Chrome and Konqueror (#1484841)
- Fix UTF-8 byte-order mark removing (#1485514)
- Fix folders subscribtions on Konqueror (#1484841)
- Fix debug console on Konqueror and Safari
- Fix messagelist focus issue when modifying status of selected messages (#1485807)
- Support STARTTLS in IMAP connection (#1485284)
- Fix DEL key problem in search boxes (#1485528)
- Support several e-mail addresses per user from virtuser_file (#1485678)
- Fix drag&drop with scrolling on IE (#1485786)
- Fix adding signature separator in html mode (#1485350)
- Fix opening attachment marks message as read (#1485803)
- Fix 'temp_dir' does not support relative path under Windows (#1484529)
- Fix "Initialize Database" button missing from installer (#1485802)
- Fix compose window doesn't fit 1024x768 window (#1485396)
- Fix service not available error when pressing back from compose dialog (#1485552)
- Fix using mail() on Windows (#1485779)
- Fix word wrapping in message-part's <PRE>s for printing (#1485787)
- Fix incorrect word wrapping in outgoing plaintext multibyte messages (#1485714)
- Fix double footer in HTML message with embedded images
- Fix TNEF implementation bug (#1485773)
- Fix incorrect row id parsing for LDAP contacts list (#1485784)
- Fix 'mode' parameter in sqlite DSN (#1485772)
RELEASE 0.2.1
------------------
- Use US-ASCII as failover when Unicode searching fails (#1485762)
- Fix errors handling in IMAP command continuations (#1485762)
- Fix FETCH result parsing for servers returning flags at the end of result (#1485763)
- Fix datetime columns defaults in mysql's DDL (#1485641)
- Fix attaching more than nine inline images (#1485759)
- Support 'UNICODE-1-1-UTF-7' alias for UTF-7 encoding (#1485758)
- Fix mime-type detection using a hard-coded map (#1485311)
- Don't return empty string if charset conversion failed (#1485757)
- Disable concurrent autocomplete query results display (#1485743)
- Fix new lines stripped from message footer (#1485751)
- Fix IE problem with mouse click autocomplete (#1485739)
- Fix html body washing on reply/forward + fix attachments handling (#1485676)
- Fix multiple recipients input parsing (#1485733)
- Fix replying to message with html attachment (#1485676)
- Use default_charset for messages without specified charset (#1485661, #1484961)
- Support non-standard "GMT-XXXX" literal in date header (#1485729)
- Added TNEF support to decode MS Outlook attachments (winmail.dat)
- Fix "value continuation" MIME headers by adding required semicolon (#1485727)
- Fix pressing select all/unread multiple times (#1485723)
- Fix selecting all unread does not honor new messages (#1485724)
- Fix some base64 encoded attachments handling (#1485725)
- Support NGINX as IMAP backend: better BAD response handling (#1485720)
- Performance fix: don't fetch attachment parts headers twice to parse filename
- Fix checking for recent messages on various IMAP servers (#1485702)
- Performance fix: Don't fetch quota and recent messages in "message view" mode
- Fix displaying of alternative-inside-alternative messages (#1485713)
- Fix MDNSent flag checking, use arbitrary keywords (asterisk) flag (#1485706)
- Fix creation of folders with '&' sign in name
- Fix parsing of email addresses without angle brackets (#1485693)
- Save spellcheck corrections when switching from plain to html editor (and spellchecking is on)
- Fix large search results on server without SORT capability (#1485668)
- Get rid of preg_replace() with eval modifier and create_function usage (#1485686)
- Bring back <base> and <link> tags in HTML messages
- Fix XSS vulnerability through background attributes [CVE-2009-0413]
- Fix problems with backslash as IMAP hierarchy delimiter (#1484467)
- Secure vcard export by getting rid of preg's 'e' modifier use (#1485689)
- Fix authentication when submitting form with existing session (#1485679)
- Allow absolute URLs to images in HTML messages/sigs (#1485666)
- Fix message body which contains both inline attachments and emotions
- Fix SQL query execution errors handling in rcube_mdb2 class (#1485509)
- Fix address names with '@' sign handling (#1485654)
- Improve messages display performance
- Fix messages searching with 'to:' modifier
RELEASE 0.2-STABLE
------------------
- Fix mark popup in IE 7 (#1485369)
- Fix line-break issue when copy & paste in Firefox (#1485425)
- Fix autocomplete "unknown server error" (#1485637)
- Fix STARTTLS before AUTH in SMTP connection (#1484883)
- Support multiple quota values in QUOTAROOT resonse (#1485626)
- Only abbreviate file name for IE < 7 browsers (#1485063)
- Performance: allow setting imap rootdir and delimiter before connect (#1485172)
- Fix sorting of folders with more than 2 levels (#1485569)
- Fix search results page jumps in LDAP addressbook (#1485253)
- Fix empty line before the signature in IE (#1485351)
- Fix horizontal scrollbar in preview pane on IE (#1484633)
- Add Robots meta tag in login page and installer (#1484846)
- Added 'show_images' option, removed 'addrbook_show_images' (#1485597)
- Option to check for new mails in all folders (#1484374)
- Don't set client busy when checking for new messages (#1485276)
- Allow UTF-8 folder names in config (#1485579)
- Add junk_mbox option configuration in installer (#1485579)
- Do serverside addressbook queries for autocompletion (#1485531)
- Allow setting attachment col position in 'list_cols' option
- Allow override 'list_cols' via skin (#1485577)
- Fix 'cache' table cleanup on session destroy (#1485516)
- Increase speed of session destroy and garbage clean up
- Fix session timeout when DB server got clock skew (#1485490)
- Fix handling of some malformed messages (#1484438)
- Speed up raw message body handling
- Better HTML entities conversion in html2text (#1485519)
- Fix big memory consumption and speed up searching on servers without SORT capability
- Fix setting locale to tr_TR, ku and az_AZ (#1485470)
- Use SORT for searching on servers with SORT capability
- Added message status filter
- Fix empty file sending (#1485389)
- Improved searching with many criterias (calling one SEARCH command)
- Fix HTML editor initialization on IE (#1485304)
- Add warning when switching editor mode from html to plain (#1485488)
- Make identities list scrollable (#1485538)
- Fix problem with numeric folder names (#1485527)
- Added BYE response simple support to prevent from endless loops in imap.inc (#1483956)
- Fix unread message unintentionally marked as read if read_when_deleted=true (#1485409)
- Remove port number from SERVER_NAME in smtp_helo_host (#1485518)
- Don't send disposition notification receipts for messages marked as 'read' (#1485523)
- Added 'keep_alive' and 'min_keep_alive' options (#1485360)
- Added option 'identities_level', removed 'multiple_identities'
- Allow deleting identities when multiple_identities=false (#1485435)
- Added option focus_on_new_message (#1485374)
- Fix html2text class autoloading on Windows (#1485505)
- Fix html signature formatting when identity save error occurred (#1485426)
- Add feedback and set busy when moving folder (#1485497)
- Fix 'Empty' link visibility for some languages e.g. Slovak (#1485489)
- Fix messages count bar overlapping (#1485270)
- Fix adding signature in drafts compose mode (#1485484)
- Fix iil_C_Sort() to support very long and/or divided responses (#1485283)
- Fix matching case sensitivity when setting identity on reply (#1485480)
- Prefer default identity on reply
- Fix imap searching on ISMail server (#1485466)
- Add css class for flagged messages (#1485464)
- Write username instead of id in sendmail log (#1485477)
- Fix htmlspecialchars() use for PHP version < 5.2.3 (#1485475)
- Fix js keywords escaping in json_serialize() for IE/Opera (#1485472)
- Added bin/killcache.php script (#1485434)
- Add support for SJIS, GB2312, BIG5 in rc_detect_encoding()
- Fix vCard file encoding detection for non-UTF-8 strings (#1485410)
- Add 'skip_deleted' option in User Preferences (#1485445)
- Minimize "inline" javascript scripts use (#1485433)
- Fix css class setting for folders with names matching defined classes names (#1485355)
- Fix race conditions when changing mailbox
- Fix spellchecking when switching to html editor (#1485362)
- Fix compose window width/height (#1485396)
- Allow calling msgimport.sh/msgexport.sh from any directory (#1485431)
- Localized filesize units (#1485340)
- Better handling of "no identity" and "no email in identity" situations (#1485117)
- Added 'mime_param_folding' option with possibility to choose long/non-ascii attachment names encoding eg. to be readable in MS Outlook/OE (#1485320)
- Added "advanced options" feature in User Preferences
- Fix unread counter when displaying cached massage in preview panel (#1485290)
- Fix htmleditor spellchecking on MS Windows (#1485397)
- Fix problem with non-ascii attachment names in Mail_mime (#1485267, #1485096)
- Fix language autodetection (#1485401)
- Fix button label in folders management (#1485405)
- Fix collapsed folder not indicating unread msgs count of all subfolders (#1485403)
- Fix handling of apostrophes in filenames decoded according to rfc2231
RELEASE 0.2-BETA
----------------
- Made config files location configurable (#1485215)
- Reduced memory footprint when forwarding attachments (#1485345)
- Allow and use spellcheck attribute for input/textarea fields (#1485060)
- Added icons for forwarded/forwarded+replied messages (#1485257)
- Added Reply-To to forwarded emails (#1485315)
- Display progress message for folders create/delete/rename (#1485357)
- Smart Tags and NOBR tag support in html messages (#1485363, #1485327)
- Redesign of the identities settings (#1484042)
- Add config option to disable creation/deletion of identities (#1484498)
- Added 'sendmail_delay' option to restrict messages sending interval (#1484491)
- Added vertical splitter for folders list resizing
- Added possibility to view all headers in message view
- Fixed splitter drag/resize on Opera (#1485170)
- Fixed quota img height/width setting from template (#1484857)
- Refactor drag & drop functionality. Don't rely on browser events anymore (#1484453)
- Insert "virtual" folders in subscription list (#1484779)
- Added link to open message in new window
- Enable export of address book contacts as vCard
- Add feature to import contacts from vcard files (#1326103)
- Respect Content-Location headers in multipart/related messages according to RFC2110 (#1484946)
- Allowed max. attachment size now indicated in compose screen (#1485030)
- Also capture backspace key in list mode (#1484566)
- Allow application/pgp parts to be displayed (#1484753)
- Correctly handle options in mailto-links (#1485228)
- Immediately save sort_col/sort_order in user prefs (#1485265)
- Truncate very long (above 50 characters) attachment filenames when displaying
- Allow to auto-detect client language if none set (#1484434)
- Auto-detect the client timezone (user configurable)
- Add RFC2231 header value continuations support for attachment filenames + hack for servers that not support that feature
- Fix Reply-To header displaying (#1485314)
- Mark form buttons that provide the most obvious operation (mainaction)
- Added option 'quota_zero_as_unlimited' (#1484604)
- Added PRE handling in html2text class (#1484740)
- Added folder hierarchy collapsing
- Added options to use syslog instead of log file (#1484850)
- Added Logging & Debugging section in Installer
- Fix In-Reply-To and References headers when composing saved draft message (#1485288)
- Fix html message charset conversion for charsets with underline (#1485287)
- Fix buttons status after contacts deletion (#1485233)
- Fix escaping of To: and From: fields when building message body for reply or forward in the HTML editor (#1484904)
- Use current mailbox name in template (#1485256)
- Better fix for skipping untagged responses (#1485261)
- Added pspell support patch by Kris Steinhoff (#1483960)
- Enable spellchecker for HTML editor (#1485114)
- Respect spellcheck_uri in tinyMCE spellchecker (#1484196)
- Case insensitive contacts searching using PostgreSQL (#1485259)
- Make default imap folders configurable for each user (#1485075)
- Save outgoing mail to selectable folder (#1324581)
- Fix hiding of mark menu when clicking th button again (#1484944)
- Use long date format in print mode (#1485191)
- Updated TinyMCE to version 3.1.0.1
- Re-enable autocomplete attribute for login form (#1485211)
- Check PERMANENTFLAGS before saving $MDNSent flag (#1484963, #1485163)
- Added flag column on messages list (#1484623)
- Patched Mail/MimePart.php (http://pear.php.net/bugs/bug.php?id=14232)
- Allow trash/junk subfolders to be purged (#1485085)
- Store compose parameters in session and redirect to a unique URL
- Fixed CRAM-MD5 authentication (#1484819)
- Fixed forwarding messages with one HTML attachment (#1484442)
- Fixed encoding of message/rfc822 attachments and image/pjpeg handling (#1484914)
- Added option to select skin in user preferences
- Added option to configure displaying of attached images below the message body
- Added option to display images in messages from known senders (#1484601)
- User preferences grouped in more fieldsets
- Fix corrupted MIME headers of messages in Sent folder (#1485111)
- Fixed bug in MDB2 package: http://pear.php.net/bugs/bug.php?id=14124
- Use keypress instead of keydown to select list's row (#1484816)
- Don't call expunge and don't remove message row after message move if flag_for_deletion is set to true (#1485002)
RELEASE 0.2-ALPHA
-----------------
- Added option to disable autocompletion from selected LDAP address books (#1484922)
- TLS support in LDAP connections: 'use_tls' property (#1485104)
- Fixed removing messages from search set after deleting them (#1485106)
- imap.inc: Fixed iil_C_FetchStructureString() to handle many
literal strings in response (#1484969)
- Support for subfolders in default/protected folders (#1484665)
- Disallowed delimiter in folder name (#1484803)
- Support " and \ in folder names
- Escape \ in login (#1484614)
- Better HTML sanitization with the DOM-based washtml script (#1484701)
- Fixed sorting of folders with non-ascii characters
- Fixed Mysql DDL for default identities creation (#1485070)
- In Preferences added possibility to configure 'read_when_deleted',
'mdn_requests', 'flag_for_deletion' options
- Made IMAP auth type configurable (#1483825)
- Fixed empty values with FROM_UNIXTIME() in rcube_mdb2 (#1485055)
- Fixed attachment list on IE 6/7 (#1484807)
- Fixed JavaScript in compose.html that shows cc/bcc fields if populated
- Make password input fields of type password in installer (#1484886)
- Don't attempt to delete cache entries if enable_caching is FALSE (#1485051)
- Optimized messages sorting on servers without sort capability (#1485049)
- Corrected message headers decoding when charset isn't specified and improved
support for native languages (#1485050, #1485048)
- Expanded LDAP configuration options to support LDAP server writes.
- Installer: encode special characters in DB username/password (#1485042)
- Fixed management of folders with national characters in names (#1485036, #1485001)
- Fixed identities saving when using MDB2 pgsql driver (#1485032)
- Fixed BCC header reset (#1484997)
- Improved messages list performance - patch from Justin Heesemann
- Append skin_path to images location only when it starts with '/' sign (#1484859)
- Fix IMAP response in message body when message has no body (#1484964)
- Fixed non-RFC dates formatting (#1484901)
- Fixed typo in set_charset() (#1484991)
- Decode entities when inserting HTML signature to plain text message (#1484990)
- HTML editing is now working with PHP5 updates and TinyMCE v3.0.6
- Fixed signature loading on Windows (#1484545)
- Added language support to HTML editing (#1484862)
- Fixed remove signature when replying (#1333167)
- Fixed problem with line with a space at the end (#1484916)
- Fixed <!DOCTYPE> tag filtering (#1484391)
- Fixed <?xml> tag filtering (#1484403)
- Added sections (fieldset+label) in Settings interface
- Mark as read in one action with message preview (#1484972)
- Deleted redundant quota reads (#1484972)
- Added options for empty trash and expunge inbox on logout (#1483863)
- Removed lines wrapping when displaying message
- Fixed month localization
- Changed codebase to PHP5 with autoloader
RELEASE 0.1.1
-------------
- Clear selection when selecting single item (#1484942)
- Remove hard-coded image size in skin templates (#1484893)
- Database schema improvements (dropped unnecessary indexes)
- Fixed creating a new folder with a comma in its name (#1484681)
- Fixed sorting of messages when default mailbox is empty (#1484317)
- Improve message previewpane - less loading (#1484316)
- Fixed login form autoompletion (#1484839)
- Fixed virtuser_query option for mdb2 backend (#1484874)
- Fixed attachment resoting from Drafts when message body was empty (#1484506)
- Fixed usage of ob_gzhandler (#1484851)
- Fixed message part window in IE6 (#1484610)
- Fixed decoding of mime-encoded strings (#1484191)
- Fixed some iconv/mb_string problems (#1484598)
- Correctly quote mailbox name when using in URL (#1484313)
- Fixed "headers already sent" errors (#1484860)
RELEASE 0.1-STABLE
------------------
- Added interactive installer script
- Fix folder adding/renaming inspired by #1484800
- Localize folder name in page title (#1484785)
- Fix code using wrong variable name (#1484018)
- Allow to send mail with BCC recipients only
- condense TinyMCE toolbar down to one line, removing table buttons (#1484747)
- Add function to mark the selected messages as read/unread (#1457360)
- Also do charset decoding as suggested in RFC 2231 (fix #1484321)
- Show message count in folder list and hint when creating a subfolder
- Distinguish ssl and tls for imap connections (#1484667)
- Added some charset aliases to fix typical mis-labelling (#1484565)
- Remember decision to display images for a certain message during session (#1484754)
- Truncate attachment filenames to 55 characters due to an IE bug (#1484757)
- Make sending of read receipts configurable
- Respect config when localize folder names (#1484707)
- Also respect receipt and priority settings when re-opening a draft message
- Remember search results (closes #1483883), patch by the_glu
- Add Received header on outgoing mail
- Upgrade to TinyMCE 2.1.3
- Allow inserting image attachments into HTML messages while composing (#1484557)
- Implement Message-Disposition-Notification (Receipts)
- Fix overriding of session vars when register_globals is on (#1484670)
- Fix bug with case-sensitive folder names (#1484245)
- Don't create default folders by default
- Fixed some potential security risks (audited by Andris)
- Only show new messages if they match the current search (#1484176)
- Switch to/from when searcing in Sent folder (#1484555)
- Correctly read the References header (#1484646)
- Unset old cookie before sending a new value (#1484639)
- Correctly decode attachments when downloading them (#1484645 and #1484642)
- Suppress IE errors when clearing attachments form (#1484356)
- Log error when login fails due to auto_create_user turned off
- Filter linked/imported CSS files (closes #1484056)
- Improve message compose screen (closes #1484383)
- Select next row after removing one from list (#1484387)
RELEASE 0.1-RC2
---------------
- Enable drag-&-dropping of folders to a new parent and allow to create subfolders (#1457344)
- Suppress IE errors when clearing attachments form (#1484356)
- Set preferences field in user table to NULL (#1484386)
- Log error when login fails due to auto_create_user turned off
- Filter linked/imported CSS files (closes #1484056)
- Improve message compose screen (closes #1484383)
- Select next row after removing one from list (#1484387)
- Make smtp HELO/EHLO hostname configurable (#1484067)
- IPv6 Compatability (#1484322), Patch #1484373
- Unlock interface when message sending fails (#1484570)
- Eval PHP code in template includes (if configured)
- Show message when folder is empty. Mo more static text in table (#1484395)
- Only display unread count in page title when new messages arrived
- Fixed wrong delete button tooltip (#1483965)
- Fixed charset encoding bug (#1484429)
- Applied patch for LDAP version (#1484552)
- Improved XHTML validation
- Fix message list selection (#1484550)
- Better fix lowercased usernames (#1484473)
- Update pngbehavior Script as suggested in #1484490
- Fixed moving/deleting messages when more than 1 is selected
- Applied patch for LDAP contacts listing by Glen Ogilvie
- Applied patch for more address fields in LDAP contacts (#1484402)
- Add alternative for getallheaders() (fix #1484508)
- Identify mailboxes case-sensitive
- Sort mailbox list case-insensitive (closes #1484338)
- Fix display of multipart messages from Apple Mail (closes #1484027)
- Protect AJAX request from being fetched by a foreign site (XSS)
- Make autocomplete for loginform configurable by the skin template
- Fix compose function from address book (closes #1484426)
- Added //IGNORE to iconv call (patch #1484420, closes #1484023)
- Check if mbstring supports charset (#1484290 and #1484292)
- Prefer iconv over mbstring (as suggested in #1484292)
- Check filesize of template includes (#1484409)
- Fixed bug with buttons not dimming/enabling properly after switching folders
- Fixed compose window becoming unresponsive after saving a draft (#1484487)
- Re-enabled "Back" button in compose window now that bug #1484487 is fixed
- Fixed unresponsive interface issue when downloading attachments (#1484496)
- Lowered status message time from 5 to 3 seconds to improve responsiveness
- Raised .htaccess upload_max_filesize from 2M to 5M to differ from default php.ini
- Increased "mailboxcontrols" mail.css width from 160 to 170px to fix non-english languages (#1484499)
- Fix status message bug #1484464 with regard to #1484353
- Fix address adding bug reported by David Koblas
- Applied socket error patch by Thomas Mangin
- Pass-by-reference workarround for PHP5 in sendmail.inc
- Fixed buggy imap_root settings (closes #1484379)
- Prevent default events on subject links (#1484399)
- Use HTTP-POST requests for actions that change state
RELEASE 0.1-RC1
---------------
- Use global filters and bind username/ for Ldap searches (#1484159)
- Hide quota display if imap server does not support it
- Hide address groups if no LDAP servers configured
- Add link to message subjects (closes #1484257)
- Better SQL query for contact listing/search (closes #1484369)
- Fixed marking as read in preview pane (closes #1484364)
- CSS hack to display attachments correctly in IE6
- Wrap message body text (closes #1484148)
- LDAP access is back in address book (closes #1484087)
- Added search function for contacts
- New Template parsing and output encoding
- Fixed bugs #1484119 and #1483978
- Fixed message moving procedure (closes #1484308)
- Fixed display of multiple attachments (closes #1466563)
- Fixed check for new messages (closes #1484310)
- List attachments without filename
- New session authentication: Change sessid cookie when login, authentication with sessauth cookie is now configurable.
Should close bugs #1483951 and #1484299
- Correctly translate mailbox names (closes #1484276)
- Quote e-mail address links (closes #1484300)
- Updated PEAR::Mail_mime package
- Accept single quotes for HTML attributes when modifying message body (thanks Jason)
- Sanitize input for new users/identities (thanks Colin Alston)
- Don't download HTML message parts
- Convert HTML parts to plaintext if 'prefer_html' is off
- Correctly parse message/rfc822 parts (closes #1484045)
- Also use user_id for unique key in messages table (closes #1484074)
- Hide contacts drop down on blur (closes #1484203)
- Make entries in contacts drop down clickable
- Turn off browser autocompletion on login page
- Quote <? in text/html message parts
- Hide border around radio buttons
- Applied patch for attachment download by crichardson (closes #1484198)
- Fixed bug in Postgres DB handling (closes #1484068)
- Fixed bug of invalid calls to fetchRow() in rcube_db.inc (closes #1484280)
- Fixed array_merge bug (closes #1484281)
- Fixed flag for deletion in list view (closes #1484264)
- Finally support semicolons as recipient separator (closes ##1484251)
- Fixed message headers (subject) encoding
- check if safe mode is on or not (closes #1484269)
- Show "no subject" in message list if subject is missing (closes #1484243)
- Solved page caching of message preview (closes #1484153)
- Only use gzip compression if configured (closes #1484236)
- Fixed priority selector issue (#1484150)
- Fixed some CSS issues in default skin (closes #1484210 and #1484161)
- Prevent from double quoting of numeric HTML character references (closes #1484253)
- Fixed display of HTML message attachments (closes #1484178)
- Applied patch for preview caching (closes #1484186)
- Added error handling for attachment uploads
- Use multibyte safe string functions where necessary (closes #1483988)
- 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
- Fixed wrong message listing when showing search results (closes #1484131)
- Show remote images when opening HTML message part as attachment
- 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)
- 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)
- Fixed signature delimeter character to be standard (Bug #1484035)
- 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)
- Translate foldernames in folder form (closes #1484113)
- 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
- Fetch all aliases if virtuser_query is used instead
- Re-enabled multi select of contacts (Bug #1484017)
- Enable contact editing right after creation (Bug #1459641)
- Correct UTF-7 to UTF-8 conversion if mbstring is not available
- Fixed IMAP fetch of message body (Bug #1484019)
- Fixed safe_mode problems (Bug #1418381)
- Fixed wrong header encoding (Bug #1483976)
- Made automatic draft saving configurable
- Fixed JS bug when renaming folders (Bug #1483989)
- Added quota display as image (by Brett Patterson)
- Corrected creation of a message-id
- New indentation for quoted message text
- Improved HTML validity
- Fixed URL character set (Ticket #1445501)
- Fixed saving of contact into MySQL from LDAP query results (Ticket #1483820)
- Fixed folder renaming: unsubscribe before rename (Bug #1483920)
- Finalized new message parsing (+ chaching)
- Fixed wrong usage of mbstring (Bug #1462439)
- Set default spelling language (Ticket #1483938)
- Added support for Nox Spell Server
- Re-built message parsing (Bug #1327068)
Now based on the message structure delivered by the IMAP server.
- Fixed some XSS and SQL injection issues
- Fixed charset problems with folder renaming
diff --git a/config/defaults.inc.php b/config/defaults.inc.php
index a76eec6dc..85190eb0a 100644
--- a/config/defaults.inc.php
+++ b/config/defaults.inc.php
@@ -1,1050 +1,1052 @@
<?php
/*
+-----------------------------------------------------------------------+
| Main configuration file with default settings |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2013, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
+-----------------------------------------------------------------------+
*/
$config = array();
// ----------------------------------
// SQL DATABASE
// ----------------------------------
// Database connection string (DSN) for read+write operations
// Format (compatible with PEAR MDB2): db_provider://user:password@host/database
// Currently supported db_providers: mysql, pgsql, sqlite, mssql or sqlsrv
// For examples see http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php
// NOTE: for SQLite use absolute path: 'sqlite:////full/path/to/sqlite.db?mode=0646'
$config['db_dsnw'] = 'mysql://roundcube:@localhost/roundcubemail';
// Database DSN for read-only operations (if empty write database will be used)
// useful for database replication
$config['db_dsnr'] = '';
// Disable the use of already established dsnw connections for subsequent reads
$config['db_dsnw_noread'] = false;
// use persistent db-connections
// beware this will not "always" work as expected
// see: http://www.php.net/manual/en/features.persistent-connections.php
$config['db_persistent'] = false;
// you can define specific table (and sequence) names prefix
$config['db_prefix'] = '';
// Mapping of table names and connections to use for ALL operations.
// This can be used in a setup with replicated databases and a DB master
// where read/write access to cache tables should not go to master.
$config['db_table_dsn'] = array(
// 'cache' => 'r',
// 'cache_index' => 'r',
// 'cache_thread' => 'r',
// 'cache_messages' => 'r',
);
// ----------------------------------
// LOGGING/DEBUGGING
// ----------------------------------
// system error reporting, sum of: 1 = log; 4 = show
$config['debug_level'] = 1;
// log driver: 'syslog' or 'file'.
$config['log_driver'] = 'file';
// date format for log entries
// (read http://php.net/manual/en/function.date.php for all format characters)
$config['log_date_format'] = 'd-M-Y H:i:s O';
// Syslog ident string to use, if using the 'syslog' log driver.
$config['syslog_id'] = 'roundcube';
// Syslog facility to use, if using the 'syslog' log driver.
// For possible values see installer or http://php.net/manual/en/function.openlog.php
$config['syslog_facility'] = LOG_USER;
// Activate this option if logs should be written to per-user directories.
// Data will only be logged if a directry <log_dir>/<username>/ exists and is writable.
$config['per_user_logging'] = false;
// Log sent messages to <log_dir>/sendmail or to syslog
$config['smtp_log'] = true;
// Log successful/failed logins to <log_dir>/userlogins or to syslog
$config['log_logins'] = false;
// Log session authentication errors to <log_dir>/session or to syslog
$config['log_session'] = false;
// Log SQL queries to <log_dir>/sql or to syslog
$config['sql_debug'] = false;
// Log IMAP conversation to <log_dir>/imap or to syslog
$config['imap_debug'] = false;
// Log LDAP conversation to <log_dir>/ldap or to syslog
$config['ldap_debug'] = false;
// Log SMTP conversation to <log_dir>/smtp or to syslog
$config['smtp_debug'] = false;
// ----------------------------------
// IMAP
// ----------------------------------
// The mail host chosen to perform the log-in.
// Leave blank to show a textbox at login, give a list of hosts
// to display a pulldown menu or set one host as string.
// To use SSL/TLS connection, enter hostname with prefix ssl:// or tls://
// Supported replacement variables:
// %n - hostname ($_SERVER['SERVER_NAME'])
// %t - hostname without the first part
// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part)
// %s - domain name after the '@' from e-mail address provided at login screen
// For example %n = mail.domain.tld, %t = domain.tld
// WARNING: After hostname change update of mail_host column in users table is
// required to match old user data records with the new host.
$config['default_host'] = 'localhost';
// TCP port used for IMAP connections
$config['default_port'] = 143;
// IMAP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or null to use
// best server supported one)
$config['imap_auth_type'] = null;
// If you know your imap's folder delimiter, you can specify it here.
// Otherwise it will be determined automatically
$config['imap_delimiter'] = null;
// If IMAP server doesn't support NAMESPACE extension, but you're
// using shared folders or personal root folder is non-empty, you'll need to
// set these options. All can be strings or arrays of strings.
// Folders need to be ended with directory separator, e.g. "INBOX."
// (special directory "~" is an exception to this rule)
// These can be used also to overwrite server's namespaces
$config['imap_ns_personal'] = null;
$config['imap_ns_other'] = null;
$config['imap_ns_shared'] = null;
// By default IMAP capabilities are readed after connection to IMAP server
// In some cases, e.g. when using IMAP proxy, there's a need to refresh the list
// after login. Set to True if you've got this case.
$config['imap_force_caps'] = false;
// By default list of subscribed folders is determined using LIST-EXTENDED
// extension if available. Some servers (dovecot 1.x) returns wrong results
// for shared namespaces in this case. http://trac.roundcube.net/ticket/1486225
// Enable this option to force LSUB command usage instead.
// Deprecated: Use imap_disabled_caps = array('LIST-EXTENDED')
$config['imap_force_lsub'] = false;
// Some server configurations (e.g. Courier) doesn't list folders in all namespaces
// Enable this option to force listing of folders in all namespaces
$config['imap_force_ns'] = false;
// List of disabled imap extensions.
// Use if your IMAP server has broken implementation of some feature
// and you can't remove it from CAPABILITY string on server-side.
// For example UW-IMAP server has broken ESEARCH.
// Note: Because the list is cached, re-login is required after change.
$config['imap_disabled_caps'] = array();
// IMAP connection timeout, in seconds. Default: 0 (use default_socket_timeout)
$config['imap_timeout'] = 0;
// Optional IMAP authentication identifier to be used as authorization proxy
$config['imap_auth_cid'] = null;
// Optional IMAP authentication password to be used for imap_auth_cid
$config['imap_auth_pw'] = null;
// Type of IMAP indexes cache. Supported values: 'db', 'apc' and 'memcache'.
$config['imap_cache'] = null;
// Enables messages cache. Only 'db' cache is supported.
$config['messages_cache'] = false;
// Lifetime of IMAP indexes cache. Possible units: s, m, h, d, w
$config['imap_cache_ttl'] = '10d';
// Lifetime of messages cache. Possible units: s, m, h, d, w
$config['messages_cache_ttl'] = '10d';
// Maximum cached message size in kilobytes.
// Note: On MySQL this should be less than (max_allowed_packet - 30%)
$config['messages_cache_threshold'] = 50;
// ----------------------------------
// SMTP
// ----------------------------------
// SMTP server host (for sending mails).
// To use SSL/TLS connection, enter hostname with prefix ssl:// or tls://
// If left blank, the PHP mail() function is used
// Supported replacement variables:
// %h - user's IMAP hostname
// %n - hostname ($_SERVER['SERVER_NAME'])
// %t - hostname without the first part
// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part)
// %z - IMAP domain (IMAP hostname without the first part)
// For example %n = mail.domain.tld, %t = domain.tld
$config['smtp_server'] = '';
// SMTP port (default is 25; use 587 for STARTTLS or 465 for the
// deprecated SSL over SMTP (aka SMTPS))
$config['smtp_port'] = 25;
// SMTP username (if required) if you use %u as the username Roundcube
// will use the current username for login
$config['smtp_user'] = '';
// SMTP password (if required) if you use %p as the password Roundcube
// will use the current user's password for login
$config['smtp_pass'] = '';
// SMTP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or empty to use
// best server supported one)
$config['smtp_auth_type'] = '';
// Optional SMTP authentication identifier to be used as authorization proxy
$config['smtp_auth_cid'] = null;
// Optional SMTP authentication password to be used for smtp_auth_cid
$config['smtp_auth_pw'] = null;
// SMTP HELO host
// Hostname to give to the remote server for SMTP 'HELO' or 'EHLO' messages
// Leave this blank and you will get the server variable 'server_name' or
// localhost if that isn't defined.
$config['smtp_helo_host'] = '';
// SMTP connection timeout, in seconds. Default: 0 (use default_socket_timeout)
// Note: There's a known issue where using ssl connection with
// timeout > 0 causes connection errors (https://bugs.php.net/bug.php?id=54511)
$config['smtp_timeout'] = 0;
// SMTP socket context options
// See http://php.net/manual/en/context.ssl.php
// The example below enables server certificate validation, and
// requires 'smtp_timeout' to be non zero.
// $config['smtp_conn_options'] = array(
// 'ssl' => array(
// 'verify_peer' => true,
// 'verify_depth => 3,
// 'cafile' => '/etc/openssl/certs/ca.crt',
// ),
// );
$config['smtp_conn_options'] = null;
// ----------------------------------
// LDAP
// ----------------------------------
// Type of LDAP cache. Supported values: 'db', 'apc' and 'memcache'.
$config['ldap_cache'] = 'db';
// Lifetime of LDAP cache. Possible units: s, m, h, d, w
$config['ldap_cache_ttl'] = '10m';
// ----------------------------------
// SYSTEM
// ----------------------------------
// THIS OPTION WILL ALLOW THE INSTALLER TO RUN AND CAN EXPOSE SENSITIVE CONFIG DATA.
// ONLY ENABLE IT IF YOU'RE REALLY SURE WHAT YOU'RE DOING!
$config['enable_installer'] = false;
// don't allow these settings to be overriden by the user
$config['dont_override'] = array();
// define which settings should be listed under the 'advanced' block
// which is hidden by default
$config['advanced_prefs'] = array();
// provide an URL where a user can get support for this Roundcube installation
// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
$config['support_url'] = '';
// replace Roundcube logo with this image
// specify an URL relative to the document root of this Roundcube installation
// an array can be used to specify different logos for specific template files, '*' for default logo
// for example array("*" => "/images/roundcube_logo.png", "messageprint" => "/images/roundcube_logo_print.png")
$config['skin_logo'] = null;
// automatically create a new Roundcube user when log-in the first time.
// a new user will be created once the IMAP login succeeds.
// set to false if only registered users can use this service
$config['auto_create_user'] = true;
// Enables possibility to log in using email address from user identities
$config['user_aliases'] = false;
// use this folder to store log files (must be writeable for apache user)
// This is used by the 'file' log driver.
$config['log_dir'] = RCUBE_INSTALL_PATH . 'logs/';
// use this folder to store temp files (must be writeable for apache user)
$config['temp_dir'] = RCUBE_INSTALL_PATH . 'temp/';
// expire files in temp_dir after 48 hours
// possible units: s, m, h, d, w
$config['temp_dir_ttl'] = '48h';
// enforce connections over https
// with this option enabled, all non-secure connections will be redirected.
// set the port for the ssl connection as value of this option if it differs from the default 443
$config['force_https'] = false;
// tell PHP that it should work as under secure connection
// even if it doesn't recognize it as secure ($_SERVER['HTTPS'] is not set)
// e.g. when you're running Roundcube behind a https proxy
// this option is mutually exclusive to 'force_https' and only either one of them should be set to true.
$config['use_https'] = false;
// Allow browser-autocompletion on login form.
// 0 - disabled, 1 - username and host only, 2 - username, host, password
$config['login_autocomplete'] = 0;
// Forces conversion of logins to lower case.
// 0 - disabled, 1 - only domain part, 2 - domain and local part.
// If users authentication is case-insensitive this must be enabled.
// Note: After enabling it all user records need to be updated, e.g. with query:
// UPDATE users SET username = LOWER(username);
$config['login_lc'] = 2;
// Includes should be interpreted as PHP files
$config['skin_include_php'] = false;
// display software version on login screen
$config['display_version'] = false;
// Session lifetime in minutes
$config['session_lifetime'] = 10;
// Session domain: .example.org
$config['session_domain'] = '';
// Session name. Default: 'roundcube_sessid'
$config['session_name'] = null;
// Session authentication cookie name. Default: 'roundcube_sessauth'
$config['session_auth_name'] = null;
// Session path. Defaults to PHP session.cookie_path setting.
$config['session_path'] = null;
// Backend to use for session storage. Can either be 'db' (default), 'memcache' or 'php'
// If set to 'memcache', a list of servers need to be specified in 'memcache_hosts'
// Make sure the Memcache extension (http://pecl.php.net/package/memcache) version >= 2.0.0 is installed
// Setting this value to 'php' will use the default session save handler configured in PHP
$config['session_storage'] = 'db';
// Use these hosts for accessing memcached
// Define any number of hosts in the form of hostname:port or unix:///path/to/socket.file
$config['memcache_hosts'] = null; // e.g. array( 'localhost:11211', '192.168.1.12:11211', 'unix:///var/tmp/memcached.sock' );
// check client IP in session authorization
$config['ip_check'] = false;
// List of trusted proxies
// X_FORWARDED_* and X_REAL_IP headers are only accepted from these IPs
$config['proxy_whitelist'] = array();
// check referer of incoming requests
$config['referer_check'] = false;
// X-Frame-Options HTTP header value sent to prevent from Clickjacking.
// Possible values: sameorigin|deny. Set to false in order to disable sending them
$config['x_frame_options'] = 'sameorigin';
// this key is used to encrypt the users imap password which is stored
// in the session record (and the client cookie if remember password is enabled).
// please provide a string of exactly 24 chars.
$config['des_key'] = 'rcmail-!24ByteDESkey*Str';
// Automatically add this domain to user names for login
// Only for IMAP servers that require full e-mail addresses for login
// Specify an array with 'host' => 'domain' values to support multiple hosts
// Supported replacement variables:
// %h - user's IMAP hostname
// %n - hostname ($_SERVER['SERVER_NAME'])
// %t - hostname without the first part
// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part)
// %z - IMAP domain (IMAP hostname without the first part)
// For example %n = mail.domain.tld, %t = domain.tld
$config['username_domain'] = '';
// Force domain configured in username_domain to be used for login.
// Any domain in username will be replaced by username_domain.
$config['username_domain_forced'] = false;
// This domain will be used to form e-mail addresses of new users
// Specify an array with 'host' => 'domain' values to support multiple hosts
// Supported replacement variables:
// %h - user's IMAP hostname
// %n - http hostname ($_SERVER['SERVER_NAME'])
// %d - domain (http hostname without the first part)
// %z - IMAP domain (IMAP hostname without the first part)
// For example %n = mail.domain.tld, %t = domain.tld
$config['mail_domain'] = '';
// Password charset.
// Use it if your authentication backend doesn't support UTF-8.
// Defaults to ISO-8859-1 for backward compatibility
$config['password_charset'] = 'ISO-8859-1';
// How many seconds must pass between emails sent by a user
$config['sendmail_delay'] = 0;
// Maximum number of recipients per message. Default: 0 (no limit)
$config['max_recipients'] = 0;
// Maximum allowednumber of members of an address group. Default: 0 (no limit)
// If 'max_recipients' is set this value should be less or equal
$config['max_group_members'] = 0;
// Name your service. This is displayed on the login screen and in the window title
$config['product_name'] = 'Roundcube Webmail';
// Add this user-agent to message headers when sending
$config['useragent'] = 'Roundcube Webmail/'.RCMAIL_VERSION;
// try to load host-specific configuration
// see http://trac.roundcube.net/wiki/Howto_Config for more details
$config['include_host_config'] = false;
// path to a text file which will be added to each sent message
// paths are relative to the Roundcube root folder
$config['generic_message_footer'] = '';
// path to a text file which will be added to each sent HTML message
// paths are relative to the Roundcube root folder
$config['generic_message_footer_html'] = '';
// add a received header to outgoing mails containing the creators IP and hostname
$config['http_received_header'] = false;
// Whether or not to encrypt the IP address and the host name
// these could, in some circles, be considered as sensitive information;
// however, for the administrator, these could be invaluable help
// when tracking down issues.
$config['http_received_header_encrypt'] = false;
// This string is used as a delimiter for message headers when sending
// a message via mail() function. Leave empty for auto-detection
$config['mail_header_delimiter'] = NULL;
// number of chars allowed for line when wrapping text.
// text wrapping is done when composing/sending messages
$config['line_length'] = 72;
// send plaintext messages as format=flowed
$config['send_format_flowed'] = true;
// According to RFC2298, return receipt envelope sender address must be empty.
// If this option is true, Roundcube will use user's identity as envelope sender for MDN responses.
$config['mdn_use_from'] = false;
// Set identities access level:
// 0 - many identities with possibility to edit all params
// 1 - many identities with possibility to edit all params but not email address
// 2 - one identity with possibility to edit all params
// 3 - one identity with possibility to edit all params but not email address
// 4 - one identity with possibility to edit only signature
$config['identities_level'] = 0;
// Mimetypes supported by the browser.
// attachments of these types will open in a preview window
// either a comma-separated list or an array: 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/pdf'
$config['client_mimetypes'] = null; # null == default
// Path to a local mime magic database file for PHPs finfo extension.
// Set to null if the default path should be used.
$config['mime_magic'] = null;
// Absolute path to a local mime.types mapping table file.
// This is used to derive mime-types from the filename extension or vice versa.
// Such a file is usually part of the apache webserver. If you don't find a file named mime.types on your system,
// download it from http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
$config['mime_types'] = null;
// path to imagemagick identify binary (if not set we'll use Imagick or GD extensions)
$config['im_identify_path'] = null;
// path to imagemagick convert binary (if not set we'll use Imagick or GD extensions)
$config['im_convert_path'] = null;
// Size of thumbnails from image attachments displayed below the message content.
// Note: whether images are displayed at all depends on the 'inline_images' option.
// Set to 0 to display images in full size.
$config['image_thumbnail_size'] = 240;
// maximum size of uploaded contact photos in pixel
$config['contact_photo_size'] = 160;
// Enable DNS checking for e-mail address validation
$config['email_dns_check'] = false;
// Disables saving sent messages in Sent folder (like gmail) (Default: false)
// Note: useful when SMTP server stores sent mail in user mailbox
$config['no_save_sent_messages'] = false;
// ----------------------------------
// PLUGINS
// ----------------------------------
// List of active plugins (in plugins/ directory)
$config['plugins'] = array();
// ----------------------------------
// USER INTERFACE
// ----------------------------------
// default messages sort column. Use empty value for default server's sorting,
// or 'arrival', 'date', 'subject', 'from', 'to', 'fromto', 'size', 'cc'
$config['message_sort_col'] = '';
// default messages sort order
$config['message_sort_order'] = 'DESC';
// These cols are shown in the message list. Available cols are:
// subject, from, to, fromto, cc, replyto, date, size, status, flag, attachment, 'priority'
$config['list_cols'] = array('subject', 'status', 'fromto', 'date', 'size', 'flag', 'attachment');
// the default locale setting (leave empty for auto-detection)
// RFC1766 formatted language name like en_US, de_DE, de_CH, fr_FR, pt_BR
$config['language'] = null;
// use this format for date display (date or strftime format)
$config['date_format'] = 'Y-m-d';
// give this choice of date formats to the user to select from
// Note: do not use ambiguous formats like m/d/Y
$config['date_formats'] = array('Y-m-d', 'Y/m/d', 'Y.m.d', 'd-m-Y', 'd/m/Y', 'd.m.Y', 'j.n.Y');
// use this format for time display (date or strftime format)
$config['time_format'] = 'H:i';
// give this choice of time formats to the user to select from
$config['time_formats'] = array('G:i', 'H:i', 'g:i a', 'h:i A');
// use this format for short date display (derived from date_format and time_format)
$config['date_short'] = 'D H:i';
// use this format for detailed date/time formatting (derived from date_format and time_format)
$config['date_long'] = 'Y-m-d H:i';
// store draft message is this mailbox
// leave blank if draft messages should not be stored
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$config['drafts_mbox'] = 'Drafts';
// store spam messages in this mailbox
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$config['junk_mbox'] = 'Junk';
// store sent message is this mailbox
// leave blank if sent messages should not be stored
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$config['sent_mbox'] = 'Sent';
// move messages to this folder when deleting them
// leave blank if they should be deleted directly
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$config['trash_mbox'] = 'Trash';
// automatically create the above listed default folders on first login
$config['create_default_folders'] = false;
// protect the default folders from renames, deletes, and subscription changes
$config['protect_default_folders'] = true;
// Disable localization of the default folder names listed above
$config['show_real_foldernames'] = false;
// if in your system 0 quota means no limit set this option to true
$config['quota_zero_as_unlimited'] = false;
// Make use of the built-in spell checker. It is based on GoogieSpell.
// Since Google only accepts connections over https your PHP installatation
// requires to be compiled with Open SSL support
$config['enable_spellcheck'] = true;
// Enables spellchecker exceptions dictionary.
// Setting it to 'shared' will make the dictionary shared by all users.
$config['spellcheck_dictionary'] = false;
// Set the spell checking engine. Possible values:
// - 'googie' - the default
// - 'pspell' - requires the PHP Pspell module and aspell installed
// - 'enchant' - requires the PHP Enchant module
// - 'atd' - install your own After the Deadline server or check with the people at http://www.afterthedeadline.com before using their API
// Since Google shut down their public spell checking service, you need to
// connect to a Nox Spell Server when using 'googie' here. Therefore specify the 'spellcheck_uri'
$config['spellcheck_engine'] = 'googie';
// For locally installed Nox Spell Server or After the Deadline services,
// please specify the URI to call it.
// Get Nox Spell Server from http://orangoo.com/labs/?page_id=72 or
// the After the Deadline package from http://www.afterthedeadline.com.
// Leave empty to use the public API of service.afterthedeadline.com
$config['spellcheck_uri'] = '';
// These languages can be selected for spell checking.
// Configure as a PHP style hash array: array('en'=>'English', 'de'=>'Deutsch');
// Leave empty for default set of available language.
$config['spellcheck_languages'] = NULL;
// Makes that words with all letters capitalized will be ignored (e.g. GOOGLE)
$config['spellcheck_ignore_caps'] = false;
// Makes that words with numbers will be ignored (e.g. g00gle)
$config['spellcheck_ignore_nums'] = false;
// Makes that words with symbols will be ignored (e.g. g@@gle)
$config['spellcheck_ignore_syms'] = false;
// Use this char/string to separate recipients when composing a new message
$config['recipients_separator'] = ',';
// don't let users set pagesize to more than this value if set
$config['max_pagesize'] = 200;
// Minimal value of user's 'refresh_interval' setting (in seconds)
$config['min_refresh_interval'] = 60;
// Enables files upload indicator. Requires APC installed and enabled apc.rfc1867 option.
// By default refresh time is set to 1 second. You can set this value to true
// or any integer value indicating number of seconds.
$config['upload_progress'] = false;
// Specifies for how many seconds the Undo button will be available
// after object delete action. Currently used with supporting address book sources.
// Setting it to 0, disables the feature.
$config['undo_timeout'] = 0;
// A static list of canned responses which are immutable for the user
$config['compose_responses_static'] = array(
// array('name' => 'Canned Response 1', 'text' => 'Static Response One'),
// array('name' => 'Canned Response 2', 'text' => 'Static Response Two'),
);
// ----------------------------------
// ADDRESSBOOK SETTINGS
// ----------------------------------
// This indicates which type of address book to use. Possible choises:
// 'sql' (default), 'ldap' and ''.
// If set to 'ldap' then it will look at using the first writable LDAP
// address book as the primary address book and it will not display the
// SQL address book in the 'Address Book' view.
// If set to '' then no address book will be displayed or only the
// addressbook which is created by a plugin (like CardDAV).
$config['address_book_type'] = 'sql';
// In order to enable public ldap search, configure an array like the Verisign
// example further below. if you would like to test, simply uncomment the example.
// Array key must contain only safe characters, ie. a-zA-Z0-9_
$config['ldap_public'] = array();
// If you are going to use LDAP for individual address books, you will need to
// set 'user_specific' to true and use the variables to generate the appropriate DNs to access it.
//
// The recommended directory structure for LDAP is to store all the address book entries
// under the users main entry, e.g.:
//
// o=root
// ou=people
// uid=user@domain
// mail=contact@contactdomain
//
// So the base_dn would be uid=%fu,ou=people,o=root
// The bind_dn would be the same as based_dn or some super user login.
/*
* example config for Verisign directory
*
$config['ldap_public']['Verisign'] = array(
'name' => 'Verisign.com',
// Replacement variables supported in host names:
// %h - user's IMAP hostname
// %n - hostname ($_SERVER['SERVER_NAME'])
// %t - hostname without the first part
// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part)
// %z - IMAP domain (IMAP hostname without the first part)
// For example %n = mail.domain.tld, %t = domain.tld
'hosts' => array('directory.verisign.com'),
'port' => 389,
'use_tls' => false,
'ldap_version' => 3, // using LDAPv3
'network_timeout' => 10, // The timeout (in seconds) for connect + bind arrempts. This is only supported in PHP >= 5.3.0 with OpenLDAP 2.x
'user_specific' => false, // If true the base_dn, bind_dn and bind_pass default to the user's IMAP login.
// When 'user_specific' is enabled following variables can be used in base_dn/bind_dn config:
// %fu - The full username provided, assumes the username is an email
// address, uses the username_domain value if not an email address.
// %u - The username prior to the '@'.
// %d - The domain name after the '@'.
// %dc - The domain name hierarchal string e.g. "dc=test,dc=domain,dc=com"
// %dn - DN found by ldap search when search_filter/search_base_dn are used
'base_dn' => '',
'bind_dn' => '',
'bind_pass' => '',
// It's possible to bind for an individual address book
// The login name is used to search for the DN to bind with
'search_base_dn' => '',
'search_filter' => '', // e.g. '(&(objectClass=posixAccount)(uid=%u))'
// DN and password to bind as before searching for bind DN, if anonymous search is not allowed
'search_bind_dn' => '',
'search_bind_pw' => '',
// Optional map of replacement strings => attributes used when binding for an individual address book
'search_bind_attrib' => array(), // e.g. array('%udc' => 'ou')
// Default for %dn variable if search doesn't return DN value
'search_dn_default' => '',
// Optional authentication identifier to be used as SASL authorization proxy
// bind_dn need to be empty
'auth_cid' => '',
// SASL authentication method (for proxy auth), e.g. DIGEST-MD5
'auth_method' => '',
// Indicates if the addressbook shall be hidden from the list.
// With this option enabled you can still search/view contacts.
'hidden' => false,
// Indicates if the addressbook shall not list contacts but only allows searching.
'searchonly' => false,
// Indicates if we can write to the LDAP directory or not.
// If writable is true then these fields need to be populated:
// LDAP_Object_Classes, required_fields, LDAP_rdn
'writable' => false,
// To create a new contact these are the object classes to specify
// (or any other classes you wish to use).
'LDAP_Object_Classes' => array('top', 'inetOrgPerson'),
// The RDN field that is used for new entries, this field needs
// to be one of the search_fields, the base of base_dn is appended
// to the RDN to insert into the LDAP directory.
'LDAP_rdn' => 'cn',
// The required fields needed to build a new contact as required by
// the object classes (can include additional fields not required by the object classes).
'required_fields' => array('cn', 'sn', 'mail'),
'search_fields' => array('mail', 'cn'), // fields to search in
// mapping of contact fields to directory attributes
// for every attribute one can specify the number of values (limit) allowed.
// default is 1, a wildcard * means unlimited
'fieldmap' => array(
// Roundcube => LDAP:limit
'name' => 'cn',
'surname' => 'sn',
'firstname' => 'givenName',
'jobtitle' => 'title',
'email' => 'mail:*',
'phone:home' => 'homePhone',
'phone:work' => 'telephoneNumber',
'phone:mobile' => 'mobile',
'phone:pager' => 'pager',
'phone:workfax' => 'facsimileTelephoneNumber',
'street' => 'street',
'zipcode' => 'postalCode',
'region' => 'st',
'locality' => 'l',
// if you country is a complex object, you need to configure 'sub_fields' below
'country' => 'c',
'organization' => 'o',
'department' => 'ou',
'jobtitle' => 'title',
'notes' => 'description',
'photo' => 'jpegPhoto',
// these currently don't work:
// 'manager' => 'manager',
// 'assistant' => 'secretary',
),
// Map of contact sub-objects (attribute name => objectClass(es)), e.g. 'c' => 'country'
'sub_fields' => array(),
// Generate values for the following LDAP attributes automatically when creating a new record
'autovalues' => array(
// 'uid' => 'md5(microtime())', // You may specify PHP code snippets which are then eval'ed
// 'mail' => '{givenname}.{sn}@mydomain.com', // or composite strings with placeholders for existing attributes
),
'sort' => 'cn', // The field to sort the listing by.
'scope' => 'sub', // search mode: sub|base|list
'filter' => '(objectClass=inetOrgPerson)', // used for basic listing (if not empty) and will be &'d with search queries. example: status=act
'fuzzy_search' => true, // server allows wildcard search
'vlv' => false, // Enable Virtual List View to more efficiently fetch paginated data (if server supports it)
'vlv_search' => false, // Use Virtual List View functions for autocompletion searches (if server supports it)
'numsub_filter' => '(objectClass=organizationalUnit)', // with VLV, we also use numSubOrdinates to query the total number of records. Set this filter to get all numSubOrdinates attributes for counting
'config_root_dn' => 'cn=config', // Root DN to search config entries (e.g. vlv indexes)
'sizelimit' => '0', // Enables you to limit the count of entries fetched. Setting this to 0 means no limit.
'timelimit' => '0', // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit.
'referrals' => false, // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups
+ 'dereference' => 0, // Sets the LDAP_OPT_DEREF option. One of: LDAP_DEREF_NEVER, LDAP_DEREF_SEARCHING, LDAP_DEREF_FINDING, LDAP_DEREF_ALWAYS
+ // Used where addressbook contains aliases to objects elsewhere in the LDAP tree.
// definition for contact groups (uncomment if no groups are supported)
// for the groups base_dn, the user replacements %fu, %u, $d and %dc work as for base_dn (see above)
// if the groups base_dn is empty, the contact base_dn is used for the groups as well
// -> in this case, assure that groups and contacts are separated due to the concernig filters!
'groups' => array(
'base_dn' => '',
'scope' => 'sub', // Search mode: sub|base|list
'filter' => '(objectClass=groupOfNames)',
'object_classes' => array('top', 'groupOfNames'), // Object classes to be assigned to new groups
'member_attr' => 'member', // Name of the default member attribute, e.g. uniqueMember
'name_attr' => 'cn', // Attribute to be used as group name
'email_attr' => 'mail', // Group email address attribute (e.g. for mailing lists)
'member_filter' => '(objectclass=*)', // Optional filter to use when querying for group members
'vlv' => false, // Use VLV controls to list groups
'class_member_attr' => array( // Mapping of group object class to member attribute used in these objects
'groupofnames' => 'member',
'groupofuniquenames' => 'uniquemember'
),
),
// this configuration replaces the regular groups listing in the directory tree with
// a hard-coded list of groups, each listing entries with the configured base DN and filter.
// if the 'groups' option from above is set, it'll be shown as the first entry with the name 'Groups'
'group_filters' => array(
'departments' => array(
'name' => 'Company Departments',
'scope' => 'list',
'base_dn' => 'ou=Groups,dc=mydomain,dc=com',
'filter' => '(|(objectclass=groupofuniquenames)(objectclass=groupofurls))',
'name_attr' => 'cn',
),
'customers' => array(
'name' => 'Customers',
'scope' => 'sub',
'base_dn' => 'ou=Customers,dc=mydomain,dc=com',
'filter' => '(objectClass=inetOrgPerson)',
'name_attr' => 'sn',
),
),
);
*/
// An ordered array of the ids of the addressbooks that should be searched
// when populating address autocomplete fields server-side. ex: array('sql','Verisign');
$config['autocomplete_addressbooks'] = array('sql');
// The minimum number of characters required to be typed in an autocomplete field
// before address books will be searched. Most useful for LDAP directories that
// may need to do lengthy results building given overly-broad searches
$config['autocomplete_min_length'] = 1;
// Number of parallel autocomplete requests.
// If there's more than one address book, n parallel (async) requests will be created,
// where each request will search in one address book. By default (0), all address
// books are searched in one request.
$config['autocomplete_threads'] = 0;
// Max. numer of entries in autocomplete popup. Default: 15.
$config['autocomplete_max'] = 15;
// show address fields in this order
// available placeholders: {street}, {locality}, {zipcode}, {country}, {region}
$config['address_template'] = '{street}<br/>{locality} {zipcode}<br/>{country} {region}';
// Matching mode for addressbook search (including autocompletion)
// 0 - partial (*abc*), default
// 1 - strict (abc)
// 2 - prefix (abc*)
// Note: For LDAP sources fuzzy_search must be enabled to use 'partial' or 'prefix' mode
$config['addressbook_search_mode'] = 0;
// ----------------------------------
// USER PREFERENCES
// ----------------------------------
// Use this charset as fallback for message decoding
$config['default_charset'] = 'ISO-8859-1';
// skin name: folder from skins/
$config['skin'] = 'larry';
// Enables using standard browser windows (that can be handled as tabs)
// instead of popup windows
$config['standard_windows'] = false;
// show up to X items in messages list view
$config['mail_pagesize'] = 50;
// show up to X items in contacts list view
$config['addressbook_pagesize'] = 50;
// sort contacts by this col (preferably either one of name, firstname, surname)
$config['addressbook_sort_col'] = 'surname';
// the way how contact names are displayed in the list
// 0: display name
// 1: (prefix) firstname middlename surname (suffix)
// 2: (prefix) surname firstname middlename (suffix)
// 3: (prefix) surname, firstname middlename (suffix)
$config['addressbook_name_listing'] = 0;
// use this timezone to display date/time
// valid timezone identifers are listed here: php.net/manual/en/timezones.php
// 'auto' will use the browser's timezone settings
$config['timezone'] = 'auto';
// prefer displaying HTML messages
$config['prefer_html'] = true;
// display remote inline images
// 0 - Never, always ask
// 1 - Ask if sender is not in address book
// 2 - Always show inline images
$config['show_images'] = 0;
// open messages in new window
$config['message_extwin'] = false;
// open message compose form in new window
$config['compose_extwin'] = false;
// compose html formatted messages by default
// 0 - never, 1 - always, 2 - on reply to HTML message, 3 - on forward or reply to HTML message
$config['htmleditor'] = 0;
// show pretty dates as standard
$config['prettydate'] = true;
// save compose message every 300 seconds (5min)
$config['draft_autosave'] = 300;
// default setting if preview pane is enabled
$config['preview_pane'] = false;
// Mark as read when viewed in preview pane (delay in seconds)
// Set to -1 if messages in preview pane should not be marked as read
$config['preview_pane_mark_read'] = 0;
// Clear Trash on logout
$config['logout_purge'] = false;
// Compact INBOX on logout
$config['logout_expunge'] = false;
// Display attached images below the message body
$config['inline_images'] = true;
// Encoding of long/non-ascii attachment names:
// 0 - Full RFC 2231 compatible
// 1 - RFC 2047 for 'name' and RFC 2231 for 'filename' parameter (Thunderbird's default)
// 2 - Full 2047 compatible
$config['mime_param_folding'] = 1;
// Set true if deleted messages should not be displayed
// This will make the application run slower
$config['skip_deleted'] = false;
// Set true to Mark deleted messages as read as well as deleted
// False means that a message's read status is not affected by marking it as deleted
$config['read_when_deleted'] = true;
// Set to true to never delete messages immediately
// Use 'Purge' to remove messages marked as deleted
$config['flag_for_deletion'] = false;
// Default interval for auto-refresh requests (in seconds)
// These are requests for system state updates e.g. checking for new messages, etc.
// Setting it to 0 disables the feature.
$config['refresh_interval'] = 60;
// If true all folders will be checked for recent messages
$config['check_all_folders'] = false;
// If true, after message delete/move, the next message will be displayed
$config['display_next'] = true;
// 0 - Do not expand threads
// 1 - Expand all threads automatically
// 2 - Expand only threads with unread messages
$config['autoexpand_threads'] = 0;
// When replying:
// -1 - don't cite the original message
// 0 - place cursor below the original message
// 1 - place cursor above original message (top posting)
$config['reply_mode'] = 0;
// When replying strip original signature from message
$config['strip_existing_sig'] = true;
// Show signature:
// 0 - Never
// 1 - Always
// 2 - New messages only
// 3 - Forwards and Replies only
$config['show_sig'] = 1;
// Use MIME encoding (quoted-printable) for 8bit characters in message body
$config['force_7bit'] = false;
// Defaults of the search field configuration.
// The array can contain a per-folder list of header fields which should be considered when searching
// The entry with key '*' stands for all folders which do not have a specific list set.
// Please note that folder names should to be in sync with $config['default_folders']
$config['search_mods'] = null; // Example: array('*' => array('subject'=>1, 'from'=>1), 'Sent' => array('subject'=>1, 'to'=>1));
// Defaults of the addressbook search field configuration.
$config['addressbook_search_mods'] = null; // Example: array('name'=>1, 'firstname'=>1, 'surname'=>1, 'email'=>1, '*'=>1);
// 'Delete always'
// This setting reflects if mail should be always deleted
// when moving to Trash fails. This is necessary in some setups
// when user is over quota and Trash is included in the quota.
$config['delete_always'] = false;
// Directly delete messages in Junk instead of moving to Trash
$config['delete_junk'] = false;
// Behavior if a received message requests a message delivery notification (read receipt)
// 0 = ask the user, 1 = send automatically, 2 = ignore (never send or ask)
// 3 = send automatically if sender is in addressbook, otherwise ask the user
// 4 = send automatically if sender is in addressbook, otherwise ignore
$config['mdn_requests'] = 0;
// Return receipt checkbox default state
$config['mdn_default'] = 0;
// Delivery Status Notification checkbox default state
// Note: This can be used only if smtp_server is non-empty
$config['dsn_default'] = 0;
// Place replies in the folder of the message being replied to
$config['reply_same_folder'] = false;
// Sets default mode of Forward feature to "forward as attachment"
$config['forward_attachment'] = false;
// Defines address book (internal index) to which new contacts will be added
// By default it is the first writeable addressbook.
// Note: Use '0' for built-in address book.
$config['default_addressbook'] = null;
// Enables spell checking before sending a message.
$config['spellcheck_before_send'] = false;
// Skip alternative email addresses in autocompletion (show one address per contact)
$config['autocomplete_single'] = false;
// Default font for composed HTML message.
// Supported values: Andale Mono, Arial, Arial Black, Book Antiqua, Courier New,
// Georgia, Helvetica, Impact, Tahoma, Terminal, Times New Roman, Trebuchet MS, Verdana
$config['default_font'] = 'Verdana';
// Default font size for composed HTML message.
// Supported sizes: 8pt, 10pt, 12pt, 14pt, 18pt, 24pt, 36pt
$config['default_font_size'] = '10pt';
// Enables display of email address with name instead of a name (and address in title)
$config['message_show_email'] = false;
// Default behavior of Reply-All button:
// 0 - Reply-All always
// 1 - Reply-List if mailing list is detected
$config['reply_all_mode'] = 0;
diff --git a/index.php b/index.php
index 239dc004f..3674db1d7 100644
--- a/index.php
+++ b/index.php
@@ -1,324 +1,324 @@
<?php
/*
+-------------------------------------------------------------------------+
| Roundcube Webmail IMAP Client |
| Version 1.1-git |
| |
| Copyright (C) 2005-2014, The Roundcube Dev Team |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License (with exceptions |
| for skins & plugins) as published by the Free Software Foundation, |
| either version 3 of the License, or (at your option) any later version. |
| |
| This file forms part of the Roundcube Webmail Software for which the |
| following exception is added: Plugins and Skins which merely make |
| function calls to the Roundcube Webmail Software, and for that purpose |
| include it by reference shall not be considered modifications of |
| the software. |
| |
| If you wish to use this file in another project or create a modified |
| version that will not be part of the Roundcube Webmail Software, you |
| may remove the exception above and use this source code under the |
| original version of the license. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see http://www.gnu.org/licenses/. |
| |
+-------------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
// include environment
require_once 'program/include/iniset.php';
// init application, start session, init output class, etc.
$RCMAIL = rcmail::get_instance($GLOBALS['env']);
// Make the whole PHP output non-cacheable (#1487797)
$RCMAIL->output->nocacheing_headers();
// turn on output buffering
ob_start();
// check if config files had errors
if ($err_str = $RCMAIL->config->get_error()) {
rcmail::raise_error(array(
'code' => 601,
'type' => 'php',
'message' => $err_str), false, true);
}
// check DB connections and exit on failure
if ($err_str = $RCMAIL->db->is_error()) {
rcmail::raise_error(array(
'code' => 603,
'type' => 'db',
'message' => $err_str), FALSE, TRUE);
}
// error steps
if ($RCMAIL->action == 'error' && !empty($_GET['_code'])) {
rcmail::raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
}
// check if https is required (for login) and redirect if necessary
if (empty($_SESSION['user_id']) && ($force_https = $RCMAIL->config->get('force_https', false))) {
$https_port = is_bool($force_https) ? 443 : $force_https;
if (!rcube_utils::https_check($https_port)) {
$host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']);
$host .= ($https_port != 443 ? ':' . $https_port : '');
header('Location: https://' . $host . $_SERVER['REQUEST_URI']);
exit;
}
}
// trigger startup plugin hook
$startup = $RCMAIL->plugins->exec_hook('startup', array('task' => $RCMAIL->task, 'action' => $RCMAIL->action));
$RCMAIL->set_task($startup['task']);
$RCMAIL->action = $startup['action'];
// try to log in
if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
$request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_utils::INPUT_POST, 'login');
// purge the session in case of new login when a session already exists
$RCMAIL->kill_session();
$auth = $RCMAIL->plugins->exec_hook('authenticate', array(
'host' => $RCMAIL->autoselect_host(),
'user' => trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST)),
'pass' => rcube_utils::get_input_value('_pass', rcube_utils::INPUT_POST, true,
$RCMAIL->config->get('password_charset', 'ISO-8859-1')),
'cookiecheck' => true,
'valid' => $request_valid,
));
// Login
if ($auth['valid'] && !$auth['abort']
&& $RCMAIL->login($auth['user'], $auth['pass'], $auth['host'], $auth['cookiecheck'])
) {
// create new session ID, don't destroy the current session
// it was destroyed already by $RCMAIL->kill_session() above
$RCMAIL->session->remove('temp');
$RCMAIL->session->regenerate_id(false);
// send auth cookie if necessary
$RCMAIL->session->set_auth_cookie();
// log successful login
$RCMAIL->log_login();
// restore original request parameters
$query = array();
if ($url = rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST)) {
parse_str($url, $query);
// prevent endless looping on login page
if ($query['_task'] == 'login') {
unset($query['_task']);
}
// prevent redirect to compose with specified ID (#1488226)
if ($query['_action'] == 'compose' && !empty($query['_id'])) {
$query = array();
}
}
// allow plugins to control the redirect url after login success
$redir = $RCMAIL->plugins->exec_hook('login_after', $query + array('_task' => 'mail'));
unset($redir['abort'], $redir['_err']);
// send redirect
$OUTPUT->redirect($redir);
}
else {
if (!$auth['valid']) {
$error_code = RCMAIL::ERROR_INVALID_REQUEST;
}
else {
$error_code = $auth['error'] ? $auth['error'] : $RCMAIL->login_error();
}
$error_labels = array(
RCMAIL::ERROR_STORAGE => 'storageerror',
RCMAIL::ERROR_COOKIES_DISABLED => 'cookiesdisabled',
RCMAIL::ERROR_INVALID_REQUEST => 'invalidrequest',
RCMAIL::ERROR_INVALID_HOST => 'invalidhost',
);
$error_message = $error_labels[$error_code] ? $error_labels[$error_code] : 'loginfailed';
$OUTPUT->show_message($error_message, 'warning');
// log failed login
$RCMAIL->log_login($auth['user'], true, $error_code);
$RCMAIL->plugins->exec_hook('login_failed', array(
'code' => $error_code, 'host' => $auth['host'], 'user' => $auth['user']));
$RCMAIL->kill_session();
}
}
// end session (after optional referer check)
else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id'])
&& (!$RCMAIL->config->get('referer_check') || rcube_utils::check_referer())
) {
$userdata = array(
'user' => $_SESSION['username'],
'host' => $_SESSION['storage_host'],
'lang' => $RCMAIL->user->language,
);
$OUTPUT->show_message('loggedout');
$RCMAIL->logout_actions();
$RCMAIL->kill_session();
$RCMAIL->plugins->exec_hook('logout_after', $userdata);
}
// check session and auth cookie
else if ($RCMAIL->task != 'login' && $_SESSION['user_id'] && $RCMAIL->action != 'send') {
if (!$RCMAIL->session->check_auth()) {
$RCMAIL->kill_session();
$session_error = true;
}
}
// not logged in -> show login page
if (empty($RCMAIL->user->ID)) {
// log session failures
$task = rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC);
if ($task && !in_array($task, array('login','logout'))
&& !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])
) {
$RCMAIL->session->log("Aborted session $sess_id; no valid session data found");
$session_error = true;
}
if ($session_error || $_REQUEST['_err'] == 'session') {
$OUTPUT->show_message('sessionerror', 'error', null, true, -1);
}
- if ($OUTPUT->ajax_call || !empty($_REQUEST['_framed'])) {
+ if ($OUTPUT->ajax_call || $OUTPUT->framed) {
$OUTPUT->command('session_error', $RCMAIL->url(array('_err' => 'session')));
$OUTPUT->send('iframe');
}
// check if installer is still active
if ($RCMAIL->config->get('enable_installer') && is_readable('./installer/index.php')) {
$OUTPUT->add_footer(html::div(array('style' => "background:#ef9398; border:2px solid #dc5757; padding:0.5em; margin:2em auto; width:50em"),
html::tag('h2', array('style' => "margin-top:0.2em"), "Installer script is still accessible") .
html::p(null, "The install script of your Roundcube installation is still stored in its default location!") .
html::p(null, "Please <b>remove</b> the whole <tt>installer</tt> folder from the Roundcube directory because .
these files may expose sensitive configuration data like server passwords and encryption keys
to the public. Make sure you cannot access the <a href=\"./installer/\">installer script</a> from your browser.")
));
}
$plugin = $RCMAIL->plugins->exec_hook('unauthenticated', array('task' => 'login', 'error' => $session_error));
$RCMAIL->set_task($plugin['task']);
$OUTPUT->send($plugin['task']);
}
// CSRF prevention
else {
// don't check for valid request tokens in these actions
$request_check_whitelist = array('login'=>1, 'spell'=>1, 'spell_html'=>1);
if (!$request_check_whitelist[$RCMAIL->action]) {
// check client X-header to verify request origin
if ($OUTPUT->ajax_call) {
if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token()) {
header('HTTP/1.1 403 Forbidden');
die("Invalid Request");
}
}
// check request token in POST form submissions
else if (!empty($_POST) && !$RCMAIL->check_request()) {
$OUTPUT->show_message('invalidrequest', 'error');
$OUTPUT->send($RCMAIL->task);
}
// check referer if configured
if ($RCMAIL->config->get('referer_check') && !rcube_utils::check_referer()) {
raise_error(array(
'code' => 403, 'type' => 'php',
'message' => "Referer check failed"), true, true);
}
}
}
// we're ready, user is authenticated and the request is safe
$plugin = $RCMAIL->plugins->exec_hook('ready', array('task' => $RCMAIL->task, 'action' => $RCMAIL->action));
$RCMAIL->set_task($plugin['task']);
$RCMAIL->action = $plugin['action'];
// handle special actions
if ($RCMAIL->action == 'keep-alive') {
$OUTPUT->reset();
$RCMAIL->plugins->exec_hook('keep_alive', array());
$OUTPUT->send();
}
else if ($RCMAIL->action == 'save-pref') {
include INSTALL_PATH . 'program/steps/utils/save_pref.inc';
}
// include task specific functions
if (is_file($incfile = INSTALL_PATH . 'program/steps/'.$RCMAIL->task.'/func.inc')) {
include_once $incfile;
}
// allow 5 "redirects" to another action
$redirects = 0; $incstep = null;
while ($redirects < 5) {
// execute a plugin action
if ($RCMAIL->plugins->is_plugin_task($RCMAIL->task)) {
if (!$RCMAIL->action) $RCMAIL->action = 'index';
$RCMAIL->plugins->exec_action($RCMAIL->task.'.'.$RCMAIL->action);
break;
}
else if (preg_match('/^plugin\./', $RCMAIL->action)) {
$RCMAIL->plugins->exec_action($RCMAIL->action);
break;
}
// try to include the step file
else if (($stepfile = $RCMAIL->get_action_file())
&& is_file($incfile = INSTALL_PATH . 'program/steps/'.$RCMAIL->task.'/'.$stepfile)
) {
// include action file only once (in case it don't exit)
include_once $incfile;
$redirects++;
}
else {
break;
}
}
if ($RCMAIL->action == 'refresh') {
$RCMAIL->plugins->exec_hook('refresh', array('last' => intval(rcube_utils::get_input_value('_last', rcube_utils::INPUT_GPC))));
}
// parse main template (default)
$OUTPUT->send($RCMAIL->task);
// if we arrive here, something went wrong
rcmail::raise_error(array(
'code' => 404,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => "Invalid request"), true, true);
diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php
index 42b208e89..2a90f6a01 100644
--- a/program/include/rcmail_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -1,1839 +1,1860 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcmail_output_html.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2006-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Class to handle HTML page output using a skin template. |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Class to create HTML page output using a skin template
*
* @package Webmail
* @subpackage View
*/
class rcmail_output_html extends rcmail_output
{
public $type = 'html';
- protected $message = null;
- protected $js_env = array();
- protected $js_labels = array();
- protected $js_commands = array();
- protected $skin_paths = array();
+ protected $message;
protected $template_name;
+ protected $js_env = array();
+ protected $js_labels = array();
+ protected $js_commands = array();
+ protected $skin_paths = array();
protected $scripts_path = '';
protected $script_files = array();
- protected $css_files = array();
- protected $scripts = array();
+ protected $css_files = array();
+ protected $scripts = array();
protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>";
protected $header = '';
protected $footer = '';
protected $body = '';
protected $base_path = '';
protected $devel_mode = false;
// deprecated names of templates used before 0.5
protected $deprecated_templates = array(
'contact' => 'showcontact',
'contactadd' => 'addcontact',
'contactedit' => 'editcontact',
'identityedit' => 'editidentity',
'messageprint' => 'printmessage',
);
/**
* Constructor
- *
- * @todo Replace $this->config with the real rcube_config object
*/
public function __construct($task = null, $framed = false)
{
parent::__construct();
$this->devel_mode = $this->config->get('devel_mode');
- //$this->framed = $framed;
$this->set_env('task', $task);
$this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
$this->set_env('standard_windows', (bool) $this->config->get('standard_windows'));
// add cookie info
$this->set_env('cookie_domain', ini_get('session.cookie_domain'));
$this->set_env('cookie_path', ini_get('session.cookie_path'));
$this->set_env('cookie_secure', filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN));
// load the correct skin (in case user-defined)
$skin = $this->config->get('skin');
$this->set_skin($skin);
$this->set_env('skin', $skin);
if (!empty($_REQUEST['_extwin']))
$this->set_env('extwin', 1);
- if ($this->framed || !empty($_REQUEST['_framed']))
+ if ($this->framed || $framed)
$this->set_env('framed', 1);
$lic = <<<EOF
/*
@licstart The following is the entire license notice for the
JavaScript code in this page.
Copyright (C) 2005-2014 The Roundcube Dev Team
The JavaScript code in this page is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
The code is distributed WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU GPL for more details.
@licend The above is the entire license notice
for the JavaScript code in this page.
*/
EOF;
// add common javascripts
$this->add_script($lic, 'head_top');
$this->add_script('var '.self::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
// don't wait for page onload. Call init at the bottom of the page (delayed)
$this->add_script(self::JS_OBJECT_NAME.'.init();', 'docready');
$this->scripts_path = 'program/js/';
$this->include_script('jquery.min.js');
$this->include_script('common.js');
$this->include_script('app.js');
// register common UI objects
$this->add_handlers(array(
'loginform' => array($this, 'login_form'),
'preloader' => array($this, 'preloader'),
'username' => array($this, 'current_username'),
'message' => array($this, 'message_container'),
'charsetselector' => array($this, 'charset_selector'),
'aboutcontent' => array($this, 'about_content'),
));
}
/**
* Set environment variable
*
* @param string Property name
* @param mixed Property value
* @param boolean True if this property should be added to client environment
*/
public function set_env($name, $value, $addtojs = true)
{
$this->env[$name] = $value;
if ($addtojs || isset($this->js_env[$name])) {
$this->js_env[$name] = $value;
}
}
/**
* Getter for the current page title
*
* @return string The page title
*/
protected function get_pagetitle()
{
if (!empty($this->pagetitle)) {
$title = $this->pagetitle;
}
else if ($this->env['task'] == 'login') {
$title = $this->app->gettext(array(
'name' => 'welcome',
'vars' => array('product' => $this->config->get('product_name')
)));
}
else {
$title = ucfirst($this->env['task']);
}
return $title;
}
/**
* Set skin
*/
public function set_skin($skin)
{
$valid = false;
$path = RCUBE_INSTALL_PATH . 'skins/';
if (!empty($skin) && is_dir($path . $skin) && is_readable($path . $skin)) {
$skin_path = 'skins/' . $skin;
$valid = true;
}
else {
$skin_path = $this->config->get('skin_path');
if (!$skin_path) {
$skin_path = 'skins/' . rcube_config::DEFAULT_SKIN;
}
$valid = !$skin;
}
$this->config->set('skin_path', $skin_path);
$this->base_path = $skin_path;
// register skin path(s)
$this->skin_paths = array();
$this->load_skin($skin_path);
return $valid;
}
/**
* Helper method to recursively read skin meta files and register search paths
*/
private function load_skin($skin_path)
{
$this->skin_paths[] = $skin_path;
// read meta file and check for dependecies
$meta = @file_get_contents(RCUBE_INSTALL_PATH . $skin_path . '/meta.json');
$meta = @json_decode($meta, true);
$meta['path'] = $skin_path;
$skin_id = end(explode('/', $skin_path));
if (!$meta['name']) {
$meta['name'] = $skin_id;
}
$this->skins[$skin_id] = $meta;
if ($meta['extends']) {
$path = RCUBE_INSTALL_PATH . 'skins/';
if (is_dir($path . $meta['extends']) && is_readable($path . $meta['extends'])) {
$this->load_skin('skins/' . $meta['extends']);
}
}
}
/**
* Check if a specific template exists
*
* @param string Template name
* @return boolean True if template exists
*/
public function template_exists($name)
{
foreach ($this->skin_paths as $skin_path) {
$filename = RCUBE_INSTALL_PATH . $skin_path . '/templates/' . $name . '.html';
if ((is_file($filename) && is_readable($filename))
|| ($this->deprecated_templates[$name] && $this->template_exists($this->deprecated_templates[$name]))
) {
return true;
}
}
return false;
}
/**
* Find the given file in the current skin path stack
*
* @param string File name/path to resolve (starting with /)
* @param string Reference to the base path of the matching skin
* @param string Additional path to search in
* @return mixed Relative path to the requested file or False if not found
*/
public function get_skin_file($file, &$skin_path = null, $add_path = null)
{
$skin_paths = $this->skin_paths;
- if ($add_path)
+ if ($add_path) {
array_unshift($skin_paths, $add_path);
+ }
foreach ($skin_paths as $skin_path) {
$path = realpath($skin_path . $file);
if (is_file($path)) {
return $skin_path . $file;
}
}
return false;
}
/**
* Register a GUI object to the client script
*
* @param string Object name
* @param string Object ID
* @return void
*/
public function add_gui_object($obj, $id)
{
$this->add_script(self::JS_OBJECT_NAME.".gui_object('$obj', '$id');");
}
/**
* Call a client method
*
* @param string Method to call
* @param ... Additional arguments
*/
public function command()
{
$cmd = func_get_args();
if (strpos($cmd[0], 'plugin.') !== false)
- $this->js_commands[] = array('triggerEvent', $cmd[0], $cmd[1]);
+ $this->js_commands[] = array('triggerEvent', $cmd[0], $cmd[1]);
else
- $this->js_commands[] = $cmd;
+ $this->js_commands[] = $cmd;
}
/**
* Add a localized label to the client environment
*/
public function add_label()
{
$args = func_get_args();
if (count($args) == 1 && is_array($args[0]))
- $args = $args[0];
+ $args = $args[0];
foreach ($args as $name) {
$this->js_labels[$name] = $this->app->gettext($name);
}
}
/**
* Invoke display_message command
*
* @param string $message Message to display
* @param string $type Message type [notice|confirm|error]
* @param array $vars Key-value pairs to be replaced in localized text
* @param boolean $override Override last set message
* @param int $timeout Message display time in seconds
* @uses self::command()
*/
public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
{
if ($override || !$this->message) {
if ($this->app->text_exists($message)) {
if (!empty($vars))
$vars = array_map('Q', $vars);
$msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
}
else
$msgtext = $message;
$this->message = $message;
$this->command('display_message', $msgtext, $type, $timeout * 1000);
}
}
/**
* Delete all stored env variables and commands
*
* @param bool $all Reset all env variables (including internal)
*/
public function reset($all = false)
{
$framed = $this->framed;
- $env = $all ? null : array_intersect_key($this->env, array('extwin'=>1, 'framed'=>1));
+ $env = $all ? null : array_intersect_key($this->env, array('extwin'=>1, 'framed'=>1));
parent::reset();
// let some env variables survive
- $this->env = $this->js_env = $env;
- $this->framed = $framed || $this->env['framed'];
+ $this->env = $this->js_env = $env;
+ $this->framed = $framed || $this->env['framed'];
$this->js_labels = array();
$this->js_commands = array();
$this->script_files = array();
$this->scripts = array();
$this->header = '';
$this->footer = '';
$this->body = '';
// load defaults
if (!$all) {
$this->__construct();
}
}
/**
* Redirect to a certain url
*
* @param mixed $p Either a string with the action or url parameters as key-value pairs
* @param int $delay Delay in seconds
*/
public function redirect($p = array(), $delay = 1)
{
if ($this->env['extwin'])
$p['extwin'] = 1;
$location = $this->app->url($p);
header('Location: ' . $location);
exit;
}
/**
* Send the request output to the client.
* This will either parse a skin tempalte or send an AJAX response
*
* @param string Template name
* @param boolean True if script should terminate (default)
*/
public function send($templ = null, $exit = true)
{
if ($templ != 'iframe') {
// prevent from endless loops
if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) {
rcube::raise_error(array('code' => 505, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => 'Recursion alert: ignoring output->send()'), true, false);
return;
}
$this->parse($templ, false);
}
else {
$this->framed = true;
$this->write();
}
// set output asap
ob_flush();
flush();
if ($exit) {
exit;
}
}
/**
* Process template and write to stdOut
*
* @param string $template HTML template content
*/
public function write($template = '')
{
// unlock interface after iframe load
$unlock = preg_replace('/[^a-z0-9]/i', '', $_REQUEST['_unlock']);
if ($this->framed) {
array_unshift($this->js_commands, array('iframe_loaded', $unlock));
}
else if ($unlock) {
array_unshift($this->js_commands, array('hide_message', $unlock));
}
- if (!empty($this->script_files))
- $this->set_env('request_token', $this->app->get_request_token());
+ if (!empty($this->script_files)) {
+ $this->set_env('request_token', $this->app->get_request_token());
+ }
+
+ $commands = $this->get_js_commands($framed);
- // write all env variables to client
- if ($commands = $this->get_js_commands()) {
- $js = $this->framed ? "if (window.parent) {\n" : '';
- $js .= $commands . ($this->framed ? ' }' : '');
- $this->add_script($js, 'head_top');
+ // if all js commands go to parent window we can ignore all
+ // script files and skip rcube_webmail initialization (#1489792)
+ if ($framed) {
+ $this->scripts = array();
+ $this->script_files = array();
}
+ // write all javascript commands
+ $this->add_script($commands, 'head_top');
+
// send clickjacking protection headers
- $iframe = $this->framed || !empty($_REQUEST['_framed']);
- if (!headers_sent() && ($xframe = $this->app->config->get('x_frame_options', 'sameorigin')))
+ $iframe = $this->framed || $this->env['framed'];
+ if (!headers_sent() && ($xframe = $this->app->config->get('x_frame_options', 'sameorigin'))) {
header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe));
+ }
// call super method
$this->_write($template, $this->config->get('skin_path'));
}
/**
* Parse a specific skin template and deliver to stdout (or return)
*
* @param string Template name
* @param boolean Exit script
* @param boolean Don't write to stdout, return parsed content instead
*
* @link http://php.net/manual/en/function.exit.php
*/
function parse($name = 'main', $exit = true, $write = true)
{
- $plugin = false;
- $realname = $name;
+ $plugin = false;
+ $realname = $name;
$this->template_name = $realname;
$temp = explode('.', $name, 2);
if (count($temp) > 1) {
- $plugin = $temp[0];
- $name = $temp[1];
- $skin_dir = $plugin . '/skins/' . $this->config->get('skin');
+ $plugin = $temp[0];
+ $name = $temp[1];
+ $skin_dir = $plugin . '/skins/' . $this->config->get('skin');
// apply skin search escalation list to plugin directory
$plugin_skin_paths = array();
foreach ($this->skin_paths as $skin_path) {
$plugin_skin_paths[] = $this->app->plugins->url . $plugin . '/' . $skin_path;
}
// add fallback to default skin
if (is_dir($this->app->plugins->dir . $plugin . '/skins/default')) {
$skin_dir = $plugin . '/skins/default';
$plugin_skin_paths[] = $this->app->plugins->url . $skin_dir;
}
// add plugin skin paths to search list
$this->skin_paths = array_merge($plugin_skin_paths, $this->skin_paths);
}
// find skin template
$path = false;
foreach ($this->skin_paths as $skin_path) {
$path = "$skin_path/templates/$name.html";
// fallback to deprecated template names
if (!is_readable($path) && $this->deprecated_templates[$realname]) {
$path = "$skin_path/templates/" . $this->deprecated_templates[$realname] . ".html";
if (is_readable($path)) {
rcube::raise_error(array(
'code' => 502, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Using deprecated template '" . $this->deprecated_templates[$realname]
. "' in $skin_path/templates. Please rename to '$realname'"),
true, false);
}
}
if (is_readable($path)) {
$this->config->set('skin_path', $skin_path);
$this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path); // set base_path to core skin directory (not plugin's skin)
$skin_dir = preg_replace('!^plugins/!', '', $skin_path);
break;
}
else {
$path = false;
}
}
// read template file
if (!$path || ($templ = @file_get_contents($path)) === false) {
rcube::raise_error(array(
'code' => 501,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => 'Error loading template for '.$realname
), true, $write);
return false;
}
// replace all path references to plugins/... with the configured plugins dir
// and /this/ to the current plugin skin directory
if ($plugin) {
$templ = preg_replace(array('/\bplugins\//', '/(["\']?)\/this\//'), array($this->app->plugins->url, '\\1'.$this->app->plugins->url.$skin_dir.'/'), $templ);
}
// parse for specialtags
$output = $this->parse_conditions($templ);
$output = $this->parse_xml($output);
// trigger generic hook where plugins can put additional content to the page
$hook = $this->app->plugins->exec_hook("render_page", array('template' => $realname, 'content' => $output));
// save some memory
$output = $hook['content'];
unset($hook['content']);
// make sure all <form> tags have a valid request token
$output = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $output);
$this->footer = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $this->footer);
if (!$write) {
return $output;
}
$this->write(trim($output));
if ($exit) {
exit;
}
}
/**
* Return executable javascript code for all registered commands
*
* @return string $out
*/
- protected function get_js_commands()
+ protected function get_js_commands(&$framed = null)
{
- $out = '';
if (!$this->framed && !empty($this->js_env)) {
- $out .= self::JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
+ $this->command('set_env', $this->js_env);
}
+
if (!empty($this->js_labels)) {
$this->command('add_label', $this->js_labels);
}
+
+ $out = '';
+ $parent_commands = 0;
+
foreach ($this->js_commands as $i => $args) {
$method = array_shift($args);
+ $parent = $this->framed || preg_match('/^parent\./', $method);
+
foreach ($args as $i => $arg) {
$args[$i] = self::json_serialize($arg);
}
- $parent = $this->framed || preg_match('/^parent\./', $method);
- $out .= sprintf(
- "%s.%s(%s);\n",
- ($parent ? 'if(window.parent && parent.'.self::JS_OBJECT_NAME.') parent.' : '') . self::JS_OBJECT_NAME,
- preg_replace('/^parent\./', '', $method),
- implode(',', $args)
- );
+
+ if ($parent) {
+ $parent_commands++;
+ $method = preg_replace('/^parent\./', '', $method);
+ $parent_prefix = 'if (window.parent && parent.' . self::JS_OBJECT_NAME . ') parent.';
+ $method = $parent_prefix . self::JS_OBJECT_NAME . '.' . $method;
+ }
+ else {
+ $method = self::JS_OBJECT_NAME . '.' . $method;
+ }
+
+ $out .= sprintf("%s(%s);\n", $method, implode(',', $args));
+ }
+
+ $framed = $parent_prefix && $parent_commands == count($this->js_commands);
+
+ // make the output more compact if all commands go to parent window
+ if ($framed) {
+ $out = "if (window.parent && parent." . self::JS_OBJECT_NAME . ") {\n"
+ . str_replace($parent_prefix, "\tparent.", $out)
+ . "}\n";
}
return $out;
}
/**
* Make URLs starting with a slash point to skin directory
*
* @param string Input string
* @param boolean True if URL should be resolved using the current skin path stack
* @return string
*/
public function abs_url($str, $search_path = false)
{
if ($str[0] == '/') {
- if ($search_path && ($file_url = $this->get_skin_file($str, $skin_path)))
+ if ($search_path && ($file_url = $this->get_skin_file($str, $skin_path))) {
return $file_url;
+ }
return $this->base_path . $str;
}
- else
- return $str;
+
+ return $str;
}
/**
* Show error page and terminate script execution
*
* @param int $code Error code
* @param string $message Error message
*/
public function raise_error($code, $message)
{
global $__page_content, $ERROR_CODE, $ERROR_MESSAGE;
$ERROR_CODE = $code;
$ERROR_MESSAGE = $message;
include RCUBE_INSTALL_PATH . 'program/steps/utils/error.inc';
exit;
}
/***** Template parsing methods *****/
/**
* Replace all strings ($varname)
* with the content of the according global variable.
*/
protected function parse_with_globals($input)
{
$GLOBALS['__version'] = html::quote(RCMAIL_VERSION);
$GLOBALS['__comm_path'] = html::quote($this->app->comm_path);
$GLOBALS['__skin_path'] = html::quote($this->base_path);
return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
array($this, 'globals_callback'), $input);
}
/**
* Callback funtion for preg_replace_callback() in parse_with_globals()
*/
protected function globals_callback($matches)
{
return $GLOBALS[$matches[1]];
}
/**
* Correct absolute paths in images and other tags
* add timestamp to .js and .css filename
*/
protected function fix_paths($output)
{
return preg_replace_callback(
'!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
array($this, 'file_callback'), $output);
}
/**
* Callback function for preg_replace_callback in write()
*
* @return string Parsed string
*/
protected function file_callback($matches)
{
$file = $matches[3];
$file = preg_replace('!^/this/!', '/', $file);
// correct absolute paths
if ($file[0] == '/') {
$file = $this->base_path . $file;
}
// add file modification timestamp
if (preg_match('/\.(js|css)$/', $file, $m)) {
$file = $this->file_mod($file);
}
return $matches[1] . '=' . $matches[2] . $file . $matches[4];
}
/**
* Modify file by adding mtime indicator
*/
protected function file_mod($file)
{
$fs = false;
$ext = substr($file, strrpos($file, '.') + 1);
// use minified file if exists (not in development mode)
if (!$this->devel_mode && !preg_match('/\.min\.' . $ext . '$/', $file)) {
$minified_file = substr($file, 0, strlen($ext) * -1) . 'min.' . $ext;
if ($fs = @filemtime($minified_file)) {
return $minified_file . '?s=' . $fs;
}
}
if ($fs = @filemtime($file)) {
$file .= '?s=' . $fs;
}
return $file;
}
/**
* Public wrapper to dipp into template parsing.
*
* @param string $input
* @return string
* @uses rcmail_output_html::parse_xml()
* @since 0.1-rc1
*/
public function just_parse($input)
{
$input = $this->parse_conditions($input);
$input = $this->parse_xml($input);
return $input;
}
/**
* Parse for conditional tags
*
* @param string $input
* @return string
*/
protected function parse_conditions($input)
{
$matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>\n?/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE);
if ($matches && count($matches) == 4) {
if (preg_match('/^(else|endif)$/i', $matches[1])) {
return $matches[0] . $this->parse_conditions($matches[3]);
}
$attrib = html::parse_attrib_string($matches[2]);
if (isset($attrib['condition'])) {
$condmet = $this->check_condition($attrib['condition']);
$submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>\n?/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
if ($condmet) {
$result = $submatches[0];
$result.= ($submatches[1] != 'endif' ? preg_replace('/.*<roundcube:endif\s+[^>]+>\n?/Uis', '', $submatches[3], 1) : $submatches[3]);
}
else {
$result = "<roundcube:$submatches[1] $submatches[2]>" . $submatches[3];
}
return $matches[0] . $this->parse_conditions($result);
}
rcube::raise_error(array(
'code' => 500,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => "Unable to parse conditional tag " . $matches[2]
), true, false);
}
return $input;
}
/**
* Determines if a given condition is met
*
* @todo Extend this to allow real conditions, not just "set"
* @param string Condition statement
* @return boolean True if condition is met, False if not
*/
protected function check_condition($condition)
{
return $this->eval_expression($condition);
}
/**
* Inserts hidden field with CSRF-prevention-token into POST forms
*/
protected function alter_form_tag($matches)
{
$out = $matches[0];
$attrib = html::parse_attrib_string($matches[1]);
if (strtolower($attrib['method']) == 'post') {
$hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token()));
$out .= "\n" . $hidden->show();
}
return $out;
}
/**
* Parse & evaluate a given expression and return its result.
*
* @param string Expression statement
*
* @return mixed Expression result
*/
protected function eval_expression ($expression)
{
$expression = preg_replace(
array(
'/session:([a-z0-9_]+)/i',
'/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i',
'/env:([a-z0-9_]+)/i',
'/request:([a-z0-9_]+)/i',
'/cookie:([a-z0-9_]+)/i',
'/browser:([a-z0-9_]+)/i',
'/template:name/i',
),
array(
"\$_SESSION['\\1']",
"\$app->config->get('\\1',rcube_utils::get_boolean('\\3'))",
"\$env['\\1']",
"rcube_utils::get_input_value('\\1', rcube_utils::INPUT_GPC)",
"\$_COOKIE['\\1']",
"\$browser->{'\\1'}",
$this->template_name,
),
$expression
);
$fn = create_function('$app,$browser,$env', "return ($expression);");
if (!$fn) {
rcube::raise_error(array(
'code' => 505,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "Expression parse error on: ($expression)"), true, false);
return null;
}
return $fn($this->app, $this->browser, $this->env);
}
/**
* Search for special tags in input and replace them
* with the appropriate content
*
* @param string Input string to parse
* @return string Altered input string
* @todo Use DOM-parser to traverse template HTML
* @todo Maybe a cache.
*/
protected function parse_xml($input)
{
return preg_replace_callback('/<roundcube:([-_a-z]+)\s+((?:[^>]|\\\\>)+)(?<!\\\\)>/Ui', array($this, 'xml_command'), $input);
}
/**
* Callback function for parsing an xml command tag
* and turn it into real html content
*
* @param array Matches array of preg_replace_callback
* @return string Tag/Object content
*/
protected function xml_command($matches)
{
$command = strtolower($matches[1]);
$attrib = html::parse_attrib_string($matches[2]);
// empty output if required condition is not met
if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
return '';
}
// execute command
switch ($command) {
// return a button
case 'button':
if ($attrib['name'] || $attrib['command']) {
return $this->button($attrib);
}
break;
// frame
case 'frame':
return $this->frame($attrib);
break;
// show a label
case 'label':
if ($attrib['expression'])
$attrib['name'] = $this->eval_expression($attrib['expression']);
if ($attrib['name'] || $attrib['command']) {
// @FIXME: 'noshow' is useless, remove?
if ($attrib['noshow']) {
return '';
}
$vars = $attrib + array('product' => $this->config->get('product_name'));
unset($vars['name'], $vars['command']);
$label = $this->app->gettext($attrib + array('vars' => $vars));
$quoting = !empty($attrib['quoting']) ? strtolower($attrib['quoting']) : (rcube_utils::get_boolean((string)$attrib['html']) ? 'no' : '');
switch ($quoting) {
case 'no':
case 'raw':
break;
case 'javascript':
case 'js':
$label = rcube::JQ($label);
break;
default:
$label = html::quote($label);
break;
}
return $label;
}
break;
// include a file
case 'include':
$old_base_path = $this->base_path;
if (!empty($attrib['skin_path'])) $attrib['skinpath'] = $attrib['skin_path'];
if ($path = $this->get_skin_file($attrib['file'], $skin_path, $attrib['skinpath'])) {
$this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path); // set base_path to core skin directory (not plugin's skin)
$path = realpath($path);
}
if (is_readable($path)) {
if ($this->config->get('skin_include_php')) {
$incl = $this->include_php($path);
}
else {
$incl = file_get_contents($path);
}
$incl = $this->parse_conditions($incl);
$incl = $this->parse_xml($incl);
$incl = $this->fix_paths($incl);
$this->base_path = $old_base_path;
return $incl;
}
break;
case 'plugin.include':
$hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
return $hook['content'];
// define a container block
case 'container':
if ($attrib['name'] && $attrib['id']) {
$this->command('gui_container', $attrib['name'], $attrib['id']);
// let plugins insert some content here
$hook = $this->app->plugins->exec_hook("template_container", $attrib);
return $hook['content'];
}
break;
// return code for a specific application object
case 'object':
$object = strtolower($attrib['name']);
$content = '';
// we are calling a class/method
if (($handler = $this->object_handlers[$object]) && is_array($handler)) {
if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) ||
(is_string($handler[0]) && class_exists($handler[0])))
$content = call_user_func($handler, $attrib);
}
// execute object handler function
else if (function_exists($handler)) {
$content = call_user_func($handler, $attrib);
}
else if ($object == 'doctype') {
$content = html::doctype($attrib['value']);
}
else if ($object == 'logo') {
$attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"')));
if ($logo = $this->config->get('skin_logo')) {
if (is_array($logo)) {
if ($template_logo = $logo[$this->template_name]) {
$attrib['src'] = $template_logo;
}
elseif ($template_logo = $logo['*']) {
$attrib['src'] = $template_logo;
}
}
else {
$attrib['src'] = $logo;
}
}
$content = html::img($attrib);
}
else if ($object == 'productname') {
$name = $this->config->get('product_name', 'Roundcube Webmail');
$content = html::quote($name);
}
else if ($object == 'version') {
$ver = (string)RCMAIL_VERSION;
if (is_file(RCUBE_INSTALL_PATH . '.svn/entries')) {
if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
$ver .= ' [SVN r'.$regs[1].']';
}
else if (is_file(RCUBE_INSTALL_PATH . '.git/index')) {
if (preg_match('/Date:\s+([^\n]+)/', @shell_exec('git log -1'), $regs)) {
if ($date = date('Ymd.Hi', strtotime($regs[1]))) {
$ver .= ' [GIT '.$date.']';
}
}
}
$content = html::quote($ver);
}
else if ($object == 'steptitle') {
$content = html::quote($this->get_pagetitle());
}
else if ($object == 'pagetitle') {
if ($this->devel_mode && !empty($_SESSION['username']))
$title = $_SESSION['username'].' :: ';
else if ($prod_name = $this->config->get('product_name'))
$title = $prod_name . ' :: ';
else
$title = '';
$title .= $this->get_pagetitle();
$content = html::quote($title);
}
// exec plugin hooks for this template object
$hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));
return $hook['content'];
// return code for a specified eval expression
case 'exp':
return html::quote($this->eval_expression($attrib['expression']));
// return variable
case 'var':
$var = explode(':', $attrib['name']);
$name = $var[1];
$value = '';
switch ($var[0]) {
case 'env':
$value = $this->env[$name];
break;
case 'config':
$value = $this->config->get($name);
if (is_array($value) && $value[$_SESSION['storage_host']]) {
$value = $value[$_SESSION['storage_host']];
}
break;
case 'request':
$value = rcube_utils::get_input_value($name, rcube_utils::INPUT_GPC);
break;
case 'session':
$value = $_SESSION[$name];
break;
case 'cookie':
$value = htmlspecialchars($_COOKIE[$name]);
break;
case 'browser':
$value = $this->browser->{$name};
break;
}
if (is_array($value)) {
$value = implode(', ', $value);
}
return html::quote($value);
case 'form':
return $this->form_tag($attrib);
}
return '';
}
/**
* Include a specific file and return it's contents
*
* @param string File path
* @return string Contents of the processed file
*/
protected function include_php($file)
{
ob_start();
include $file;
$out = ob_get_contents();
ob_end_clean();
return $out;
}
/**
* Create and register a button
*
* @param array Named button attributes
* @return string HTML button
* @todo Remove all inline JS calls and use jQuery instead.
* @todo Remove all sprintf()'s - they are pretty, but also slow.
*/
public function button($attrib)
{
static $s_button_count = 100;
// these commands can be called directly via url
$a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities');
if (!($attrib['command'] || $attrib['name'] || $attrib['href'])) {
return '';
}
// try to find out the button type
if ($attrib['type']) {
$attrib['type'] = strtolower($attrib['type']);
}
else {
$attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
}
$command = $attrib['command'];
if ($attrib['task'])
$command = $attrib['task'] . '.' . $command;
if (!$attrib['image']) {
$attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
}
if (!$attrib['id']) {
$attrib['id'] = sprintf('rcmbtn%d', $s_button_count++);
}
// get localized text for labels and titles
if ($attrib['title']) {
$attrib['title'] = html::quote($this->app->gettext($attrib['title'], $attrib['domain']));
}
if ($attrib['label']) {
$attrib['label'] = html::quote($this->app->gettext($attrib['label'], $attrib['domain']));
}
if ($attrib['alt']) {
$attrib['alt'] = html::quote($this->app->gettext($attrib['alt'], $attrib['domain']));
}
// set title to alt attribute for IE browsers
if ($this->browser->ie && !$attrib['title'] && $attrib['alt']) {
$attrib['title'] = $attrib['alt'];
}
// add empty alt attribute for XHTML compatibility
if (!isset($attrib['alt'])) {
$attrib['alt'] = '';
}
// register button in the system
if ($attrib['command']) {
$this->add_script(sprintf(
"%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
self::JS_OBJECT_NAME,
$command,
$attrib['id'],
$attrib['type'],
$attrib['imageact'] ? $this->abs_url($attrib['imageact']) : $attrib['classact'],
$attrib['imagesel'] ? $this->abs_url($attrib['imagesel']) : $attrib['classsel'],
$attrib['imageover'] ? $this->abs_url($attrib['imageover']) : ''
));
// make valid href to specific buttons
if (in_array($attrib['command'], rcmail::$main_tasks)) {
$attrib['href'] = $this->app->url(array('task' => $attrib['command']));
$attrib['onclick'] = sprintf("return %s.command('switch-task','%s',this,event)", self::JS_OBJECT_NAME, $attrib['command']);
}
else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
$attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
}
else if (in_array($attrib['command'], $a_static_commands)) {
$attrib['href'] = $this->app->url(array('action' => $attrib['command']));
}
else if (($attrib['command'] == 'permaurl' || $attrib['command'] == 'extwin') && !empty($this->env['permaurl'])) {
$attrib['href'] = $this->env['permaurl'];
}
}
// overwrite attributes
if (!$attrib['href']) {
$attrib['href'] = '#';
}
if ($attrib['task']) {
if ($attrib['classact'])
$attrib['class'] = $attrib['classact'];
}
else if ($command && !$attrib['onclick']) {
$attrib['onclick'] = sprintf(
"return %s.command('%s','%s',this,event)",
self::JS_OBJECT_NAME,
$command,
$attrib['prop']
);
}
$out = '';
// generate image tag
if ($attrib['type'] == 'image') {
$attrib_str = html::attrib_string(
$attrib,
array(
'style', 'class', 'id', 'width', 'height', 'border', 'hspace',
'vspace', 'align', 'alt', 'tabindex', 'title'
)
);
$btn_content = sprintf('<img src="%s"%s />', $this->abs_url($attrib['image']), $attrib_str);
if ($attrib['label']) {
$btn_content .= ' '.$attrib['label'];
}
$link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target');
}
else if ($attrib['type'] == 'link') {
$btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']);
$link_attrib = array_merge(html::$common_attrib, array('href', 'onclick', 'tabindex', 'target'));
if ($attrib['innerclass'])
$btn_content = html::span($attrib['innerclass'], $btn_content);
}
else if ($attrib['type'] == 'input') {
$attrib['type'] = 'button';
if ($attrib['label']) {
$attrib['value'] = $attrib['label'];
}
if ($attrib['command']) {
$attrib['disabled'] = 'disabled';
}
$out = html::tag('input', $attrib, null, array('type', 'value', 'onclick', 'id', 'class', 'style', 'tabindex', 'disabled'));
}
// generate html code for button
if ($btn_content) {
$attrib_str = html::attrib_string($attrib, $link_attrib);
$out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
}
if ($attrib['wrapper']) {
$out = html::tag($attrib['wrapper'], null, $out);
}
return $out;
}
/**
* Link an external script file
*
* @param string File URL
* @param string Target position [head|foot]
*/
public function include_script($file, $position='head')
{
if (!preg_match('|^https?://|i', $file) && $file[0] != '/') {
$file = $this->file_mod($this->scripts_path . $file);
}
if (!is_array($this->script_files[$position])) {
$this->script_files[$position] = array();
}
if (!in_array($file, $this->script_files[$position])) {
$this->script_files[$position][] = $file;
}
}
/**
* Add inline javascript code
*
* @param string JS code snippet
* @param string Target position [head|head_top|foot]
*/
public function add_script($script, $position='head')
{
if (!isset($this->scripts[$position])) {
$this->scripts[$position] = "\n" . rtrim($script);
}
else {
$this->scripts[$position] .= "\n" . rtrim($script);
}
}
/**
* Link an external css file
*
* @param string File URL
*/
public function include_css($file)
{
$this->css_files[] = $file;
}
/**
* Add HTML code to the page header
*
* @param string $str HTML code
*/
public function add_header($str)
{
$this->header .= "\n" . $str;
}
/**
* Add HTML code to the page footer
* To be added right befor </body>
*
* @param string $str HTML code
*/
public function add_footer($str)
{
$this->footer .= "\n" . $str;
}
/**
* Process template and write to stdOut
*
* @param string HTML template
* @param string Base for absolute paths
*/
public function _write($templ = '', $base_path = '')
{
$output = trim($templ);
if (empty($output)) {
- $output = $this->default_template;
+ $output = html::doctype('html5') . "\n" . $this->default_template;
$is_empty = true;
}
- // set default page title
- if (empty($this->pagetitle)) {
- $this->pagetitle = 'Roundcube Mail';
- }
-
// replace specialchars in content
$page_title = html::quote($this->pagetitle);
$page_header = '';
$page_footer = '';
// include meta tag with charset
if (!empty($this->charset)) {
if (!headers_sent()) {
header('Content-Type: text/html; charset=' . $this->charset);
}
$page_header = '<meta http-equiv="content-type"';
$page_header.= ' content="text/html; charset=';
$page_header.= $this->charset . '" />'."\n";
}
// definition of the code to be placed in the document header and footer
if (is_array($this->script_files['head'])) {
foreach ($this->script_files['head'] as $file) {
$page_header .= html::script($file);
}
}
$head_script = $this->scripts['head_top'] . $this->scripts['head'];
if (!empty($head_script)) {
$page_header .= html::script(array(), $head_script);
}
if (!empty($this->header)) {
$page_header .= $this->header;
}
// put docready commands into page footer
if (!empty($this->scripts['docready'])) {
$this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot');
}
if (is_array($this->script_files['foot'])) {
foreach ($this->script_files['foot'] as $file) {
$page_footer .= html::script($file);
}
}
if (!empty($this->footer)) {
$page_footer .= $this->footer . "\n";
}
if (!empty($this->scripts['foot'])) {
$page_footer .= html::script(array(), $this->scripts['foot']);
}
// find page header
if ($hpos = stripos($output, '</head>')) {
$page_header .= "\n";
}
else {
if (!is_numeric($hpos)) {
$hpos = stripos($output, '<body');
}
if (!is_numeric($hpos) && ($hpos = stripos($output, '<html'))) {
while ($output[$hpos] != '>') {
$hpos++;
}
$hpos++;
}
$page_header = "<head>\n<title>$page_title</title>\n$page_header\n</head>\n";
}
// add page hader
if ($hpos) {
$output = substr_replace($output, $page_header, $hpos, 0);
}
else {
$output = $page_header . $output;
}
// add page footer
if (($fpos = strripos($output, '</body>')) || ($fpos = strripos($output, '</html>'))) {
$output = substr_replace($output, $page_footer."\n", $fpos, 0);
}
else {
$output .= "\n".$page_footer;
}
// add css files in head, before scripts, for speed up with parallel downloads
if (!empty($this->css_files) && !$is_empty
&& (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>')))
) {
$css = '';
foreach ($this->css_files as $file) {
$css .= html::tag('link', array('rel' => 'stylesheet',
'type' => 'text/css', 'href' => $file, 'nl' => true));
}
$output = substr_replace($output, $css, $pos, 0);
}
$output = $this->parse_with_globals($this->fix_paths($output));
// trigger hook with final HTML content to be sent
$hook = $this->app->plugins->exec_hook("send_page", array('content' => $output));
if (!$hook['abort']) {
if ($this->charset != RCUBE_CHARSET) {
echo rcube_charset::convert($hook['content'], RCUBE_CHARSET, $this->charset);
}
else {
echo $hook['content'];
}
}
}
/**
* Returns iframe object, registers some related env variables
*
* @param array $attrib HTML attributes
* @param boolean $is_contentframe Register this iframe as the 'contentframe' gui object
* @return string IFRAME element
*/
public function frame($attrib, $is_contentframe = false)
{
static $idcount = 0;
if (!$attrib['id']) {
$attrib['id'] = 'rcmframe' . ++$idcount;
}
$attrib['name'] = $attrib['id'];
$attrib['src'] = $attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif';
// register as 'contentframe' object
if ($is_contentframe || $attrib['contentframe']) {
$this->set_env('contentframe', $attrib['contentframe'] ? $attrib['contentframe'] : $attrib['name']);
$this->set_env('blankpage', $attrib['src']);
}
return html::iframe($attrib);
}
/* ************* common functions delivering gui objects ************** */
/**
* Create a form tag with the necessary hidden fields
*
* @param array Named tag parameters
* @return string HTML code for the form
*/
public function form_tag($attrib, $content = null)
{
- if ($this->framed || !empty($_REQUEST['_framed'])) {
+ if ($this->framed || $this->env['framed']) {
$hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
$hidden = $hiddenfield->show();
}
if ($this->env['extwin']) {
$hiddenfield = new html_hiddenfield(array('name' => '_extwin', 'value' => '1'));
$hidden = $hiddenfield->show();
}
if (!$content)
$attrib['noclose'] = true;
return html::tag('form',
$attrib + array('action' => $this->app->comm_path, 'method' => "get"),
$hidden . $content,
array('id','class','style','name','method','action','enctype','onsubmit'));
}
/**
* Build a form tag with a unique request token
*
* @param array Named tag parameters including 'action' and 'task' values which will be put into hidden fields
* @param string Form content
* @return string HTML code for the form
*/
public function request_form($attrib, $content = '')
{
$hidden = new html_hiddenfield();
if ($attrib['task']) {
$hidden->add(array('name' => '_task', 'value' => $attrib['task']));
}
if ($attrib['action']) {
$hidden->add(array('name' => '_action', 'value' => $attrib['action']));
}
unset($attrib['task'], $attrib['request']);
$attrib['action'] = './';
// we already have a <form> tag
if ($attrib['form']) {
- if ($this->framed || !empty($_REQUEST['_framed']))
+ if ($this->framed || $this->env['framed'])
$hidden->add(array('name' => '_framed', 'value' => '1'));
return $hidden->show() . $content;
}
else
return $this->form_tag($attrib, $hidden->show() . $content);
}
/**
* GUI object 'username'
* Showing IMAP username of the current session
*
* @param array Named tag parameters (currently not used)
* @return string HTML code for the gui object
*/
public function current_username($attrib)
{
static $username;
// alread fetched
if (!empty($username)) {
return $username;
}
// Current username is an e-mail address
if (strpos($_SESSION['username'], '@')) {
$username = $_SESSION['username'];
}
// get e-mail address from default identity
else if ($sql_arr = $this->app->user->get_identity()) {
$username = $sql_arr['email'];
}
else {
$username = $this->app->user->get_username();
}
return rcube_utils::idn_to_utf8($username);
}
/**
* GUI object 'loginform'
* Returns code for the webmail login form
*
* @param array Named parameters
* @return string HTML code for the gui object
*/
protected function login_form($attrib)
{
$default_host = $this->config->get('default_host');
$autocomplete = (int) $this->config->get('login_autocomplete');
$_SESSION['temp'] = true;
// save original url
$url = rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST);
if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
$url = $_SERVER['QUERY_STRING'];
// Disable autocapitalization on iPad/iPhone (#1488609)
$attrib['autocapitalize'] = 'off';
// set atocomplete attribute
$user_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
$host_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
$pass_attrib = $autocomplete > 1 ? array() : array('autocomplete' => 'off');
$input_task = new html_hiddenfield(array('name' => '_task', 'value' => 'login'));
$input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
$input_tzone = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
$input_url = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
$input_user = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser', 'required' => 'required')
+ $attrib + $user_attrib);
$input_pass = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd', 'required' => 'required')
+ $attrib + $pass_attrib);
$input_host = null;
if (is_array($default_host) && count($default_host) > 1) {
$input_host = new html_select(array('name' => '_host', 'id' => 'rcmloginhost'));
foreach ($default_host as $key => $value) {
if (!is_array($value)) {
$input_host->add($value, (is_numeric($key) ? $value : $key));
}
else {
$input_host = null;
break;
}
}
}
else if (is_array($default_host) && ($host = key($default_host)) !== null) {
$hide_host = true;
$input_host = new html_hiddenfield(array(
'name' => '_host', 'id' => 'rcmloginhost', 'value' => is_numeric($host) ? $default_host[$host] : $host) + $attrib);
}
else if (empty($default_host)) {
$input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost')
+ $attrib + $host_attrib);
}
$form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
$this->add_gui_object('loginform', $form_name);
// create HTML table with two cols
$table = new html_table(array('cols' => 2));
$table->add('title', html::label('rcmloginuser', html::quote($this->app->gettext('username'))));
$table->add('input', $input_user->show(rcube_utils::get_input_value('_user', rcube_utils::INPUT_GPC)));
$table->add('title', html::label('rcmloginpwd', html::quote($this->app->gettext('password'))));
$table->add('input', $input_pass->show());
// add host selection row
if (is_object($input_host) && !$hide_host) {
$table->add('title', html::label('rcmloginhost', html::quote($this->app->gettext('server'))));
$table->add('input', $input_host->show(rcube_utils::get_input_value('_host', rcube_utils::INPUT_GPC)));
}
$out = $input_task->show();
$out .= $input_action->show();
$out .= $input_tzone->show();
$out .= $input_url->show();
$out .= $table->show();
if ($hide_host) {
$out .= $input_host->show();
}
if (rcube_utils::get_boolean($attrib['submit'])) {
$submit = new html_inputfield(array('type' => 'submit', 'id' => 'rcmloginsubmit',
'class' => 'button mainaction', 'value' => $this->app->gettext('login')));
$out .= html::p('formbuttons', $submit->show());
}
// surround html output with a form tag
if (empty($attrib['form'])) {
$out = $this->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
}
// include script for timezone detection
$this->include_script('jstz.min.js');
return $out;
}
/**
* GUI object 'preloader'
* Loads javascript code for images preloading
*
* @param array Named parameters
* @return void
*/
protected function preloader($attrib)
{
$images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY);
$images = array_map(array($this, 'abs_url'), $images);
if (empty($images) || $this->app->task == 'logout')
return;
$this->add_script('var images = ' . self::json_serialize($images) .';
for (var i=0; i<images.length; i++) {
img = new Image();
img.src = images[i];
}', 'docready');
}
/**
* GUI object 'searchform'
* Returns code for search function
*
* @param array Named parameters
* @return string HTML code for the gui object
*/
protected function search_form($attrib)
{
// add some labels to client
$this->add_label('searching');
$attrib['name'] = '_q';
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmqsearchbox';
}
if ($attrib['type'] == 'search' && !$this->browser->khtml) {
unset($attrib['type'], $attrib['results']);
}
$input_q = new html_inputfield($attrib);
$out = $input_q->show();
$this->add_gui_object('qsearchbox', $attrib['id']);
// add form tag around text field
if (empty($attrib['form'])) {
$out = $this->form_tag(array(
'name' => "rcmqsearchform",
'onsubmit' => self::JS_OBJECT_NAME . ".command('search'); return false",
'style' => "display:inline"),
$out);
}
return $out;
}
/**
* Builder for GUI object 'message'
*
* @param array Named tag parameters
* @return string HTML code for the gui object
*/
protected function message_container($attrib)
{
if (isset($attrib['id']) === false) {
$attrib['id'] = 'rcmMessageContainer';
}
$this->add_gui_object('message', $attrib['id']);
return html::div($attrib, '');
}
/**
* GUI object 'charsetselector'
*
* @param array Named parameters for the select tag
* @return string HTML code for the gui object
*/
public function charset_selector($attrib)
{
// pass the following attributes to the form class
$field_attrib = array('name' => '_charset');
foreach ($attrib as $attr => $value) {
if (in_array($attr, array('id', 'name', 'class', 'style', 'size', 'tabindex'))) {
$field_attrib[$attr] = $value;
}
}
$charsets = array(
'UTF-8' => 'UTF-8 ('.$this->app->gettext('unicode').')',
'US-ASCII' => 'ASCII ('.$this->app->gettext('english').')',
'ISO-8859-1' => 'ISO-8859-1 ('.$this->app->gettext('westerneuropean').')',
'ISO-8859-2' => 'ISO-8859-2 ('.$this->app->gettext('easterneuropean').')',
'ISO-8859-4' => 'ISO-8859-4 ('.$this->app->gettext('baltic').')',
'ISO-8859-5' => 'ISO-8859-5 ('.$this->app->gettext('cyrillic').')',
'ISO-8859-6' => 'ISO-8859-6 ('.$this->app->gettext('arabic').')',
'ISO-8859-7' => 'ISO-8859-7 ('.$this->app->gettext('greek').')',
'ISO-8859-8' => 'ISO-8859-8 ('.$this->app->gettext('hebrew').')',
'ISO-8859-9' => 'ISO-8859-9 ('.$this->app->gettext('turkish').')',
'ISO-8859-10' => 'ISO-8859-10 ('.$this->app->gettext('nordic').')',
'ISO-8859-11' => 'ISO-8859-11 ('.$this->app->gettext('thai').')',
'ISO-8859-13' => 'ISO-8859-13 ('.$this->app->gettext('baltic').')',
'ISO-8859-14' => 'ISO-8859-14 ('.$this->app->gettext('celtic').')',
'ISO-8859-15' => 'ISO-8859-15 ('.$this->app->gettext('westerneuropean').')',
'ISO-8859-16' => 'ISO-8859-16 ('.$this->app->gettext('southeasterneuropean').')',
'WINDOWS-1250' => 'Windows-1250 ('.$this->app->gettext('easterneuropean').')',
'WINDOWS-1251' => 'Windows-1251 ('.$this->app->gettext('cyrillic').')',
'WINDOWS-1252' => 'Windows-1252 ('.$this->app->gettext('westerneuropean').')',
'WINDOWS-1253' => 'Windows-1253 ('.$this->app->gettext('greek').')',
'WINDOWS-1254' => 'Windows-1254 ('.$this->app->gettext('turkish').')',
'WINDOWS-1255' => 'Windows-1255 ('.$this->app->gettext('hebrew').')',
'WINDOWS-1256' => 'Windows-1256 ('.$this->app->gettext('arabic').')',
'WINDOWS-1257' => 'Windows-1257 ('.$this->app->gettext('baltic').')',
'WINDOWS-1258' => 'Windows-1258 ('.$this->app->gettext('vietnamese').')',
'ISO-2022-JP' => 'ISO-2022-JP ('.$this->app->gettext('japanese').')',
'ISO-2022-KR' => 'ISO-2022-KR ('.$this->app->gettext('korean').')',
'ISO-2022-CN' => 'ISO-2022-CN ('.$this->app->gettext('chinese').')',
'EUC-JP' => 'EUC-JP ('.$this->app->gettext('japanese').')',
'EUC-KR' => 'EUC-KR ('.$this->app->gettext('korean').')',
'EUC-CN' => 'EUC-CN ('.$this->app->gettext('chinese').')',
'BIG5' => 'BIG5 ('.$this->app->gettext('chinese').')',
'GB2312' => 'GB2312 ('.$this->app->gettext('chinese').')',
);
if (!empty($_POST['_charset'])) {
$set = $_POST['_charset'];
}
else if (!empty($attrib['selected'])) {
$set = $attrib['selected'];
}
else {
$set = $this->get_charset();
}
$set = strtoupper($set);
if (!isset($charsets[$set])) {
$charsets[$set] = $set;
}
$select = new html_select($field_attrib);
$select->add(array_values($charsets), array_keys($charsets));
return $select->show($set);
}
/**
* Include content from config/about.<LANG>.html if available
*/
protected function about_content($attrib)
{
$content = '';
$filenames = array(
'about.' . $_SESSION['language'] . '.html',
'about.' . substr($_SESSION['language'], 0, 2) . '.html',
'about.html',
);
foreach ($filenames as $file) {
$fn = RCUBE_CONFIG_DIR . $file;
if (is_readable($fn)) {
$content = file_get_contents($fn);
$content = $this->parse_conditions($content);
$content = $this->parse_xml($content);
break;
}
}
return $content;
}
}
diff --git a/program/lib/Roundcube/rcube_ldap_generic.php b/program/lib/Roundcube/rcube_ldap_generic.php
index f1048ef39..f6f9884d8 100644
--- a/program/lib/Roundcube/rcube_ldap_generic.php
+++ b/program/lib/Roundcube/rcube_ldap_generic.php
@@ -1,1055 +1,1058 @@
<?php
/*
+-----------------------------------------------------------------------+
| Roundcube/rcube_ldap_generic.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2006-2013, The Roundcube Dev Team |
| Copyright (C) 2012-2013, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide basic functionality for accessing LDAP directories |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
/*
LDAP connection properties
--------------------------
$prop = array(
'host' => '<ldap-server-address>',
// or
'hosts' => array('directory.verisign.com'),
'port' => 389,
'use_tls' => true|false,
'ldap_version' => 3, // using LDAPv3
'auth_method' => '', // SASL authentication method (for proxy auth), e.g. DIGEST-MD5
'attributes' => array('dn'), // List of attributes to read from the server
'vlv' => false, // Enable Virtual List View to more efficiently fetch paginated data (if server supports it)
'config_root_dn' => 'cn=config', // Root DN to read config (e.g. vlv indexes) from
'numsub_filter' => '(objectClass=organizationalUnit)', // with VLV, we also use numSubOrdinates to query the total number of records. Set this filter to get all numSubOrdinates attributes for counting
'sizelimit' => '0', // Enables you to limit the count of entries fetched. Setting this to 0 means no limit.
'timelimit' => '0', // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit.
'network_timeout' => 10, // The timeout (in seconds) for connect + bind arrempts. This is only supported in PHP >= 5.3.0 with OpenLDAP 2.x
'referrals' => true|false, // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups
);
*/
/**
* Model class to access an LDAP directories
*
* @package Framework
* @subpackage LDAP
*/
class rcube_ldap_generic
{
const UPDATE_MOD_ADD = 1;
const UPDATE_MOD_DELETE = 2;
const UPDATE_MOD_REPLACE = 4;
const UPDATE_MOD_FULL = 7;
public $conn;
public $vlv_active = false;
/** private properties */
protected $cache = null;
protected $config = array();
protected $attributes = array('dn');
protected $entries = null;
protected $result = null;
protected $debug = false;
protected $list_page = 1;
protected $page_size = 10;
protected $vlv_config = null;
/**
* Object constructor
*
* @param array $p LDAP connection properties
*/
function __construct($p)
{
$this->config = $p;
if (is_array($p['attributes']))
$this->attributes = $p['attributes'];
if (!is_array($p['hosts']) && !empty($p['host']))
$this->config['hosts'] = array($p['host']);
}
/**
* Activate/deactivate debug mode
*
* @param boolean $dbg True if LDAP commands should be logged
*/
public function set_debug($dbg = true)
{
$this->debug = $dbg;
}
/**
* Set connection options
*
* @param mixed $opt Option name as string or hash array with multiple options
* @param mixed $val Option value
*/
public function set_config($opt, $val = null)
{
if (is_array($opt))
$this->config = array_merge($this->config, $opt);
else
$this->config[$opt] = $value;
}
/**
* Enable caching by passing an instance of rcube_cache to be used by this object
*
* @param object rcube_cache Instance or False to disable caching
*/
public function set_cache($cache_engine)
{
$this->cache = $cache_engine;
}
/**
* Set properties for VLV-based paging
*
* @param number $page Page number to list (starting at 1)
* @param number $size Number of entries to display on one page
*/
public function set_vlv_page($page, $size = 10)
{
$this->list_page = $page;
$this->page_size = $size;
}
/**
* Establish a connection to the LDAP server
*/
public function connect($host = null)
{
if (!function_exists('ldap_connect')) {
rcube::raise_error(array('code' => 100, 'type' => 'ldap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "No ldap support in this installation of PHP"),
true);
return false;
}
if (is_resource($this->conn) && $this->config['host'] == $host)
return true;
if (empty($this->config['ldap_version']))
$this->config['ldap_version'] = 3;
// iterate over hosts if none specified
if (!$host) {
if (!is_array($this->config['hosts']))
$this->config['hosts'] = array($this->config['hosts']);
foreach ($this->config['hosts'] as $host) {
if (!empty($host) && $this->connect($host)) {
return true;
}
}
return false;
}
// open connection to the given $host
$host = rcube_utils::idn_to_ascii(rcube_utils::parse_host($host));
$hostname = $host . ($this->config['port'] ? ':'.$this->config['port'] : '');
$this->_debug("C: Connect to $hostname [{$this->config['name']}]");
if ($lc = @ldap_connect($host, $this->config['port'])) {
if ($this->config['use_tls'] === true)
if (!ldap_start_tls($lc))
continue;
$this->_debug("S: OK");
ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->config['ldap_version']);
$this->config['host'] = $host;
$this->conn = $lc;
if (!empty($this->config['network_timeout']))
ldap_set_option($lc, LDAP_OPT_NETWORK_TIMEOUT, $this->config['network_timeout']);
if (isset($this->config['referrals']))
ldap_set_option($lc, LDAP_OPT_REFERRALS, $this->config['referrals']);
+
+ if (isset($this->config['dereference']))
+ ldap_set_option($lc, LDAP_OPT_DEREF, $this->config['dereference']);
}
else {
$this->_debug("S: NOT OK");
}
if (!is_resource($this->conn)) {
rcube::raise_error(array('code' => 100, 'type' => 'ldap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not connect to any LDAP server, last tried $hostname"),
true);
return false;
}
return true;
}
/**
* Bind connection with (SASL-) user and password
*
* @param string $authc Authentication user
* @param string $pass Bind password
* @param string $authz Autorization user
*
* @return boolean True on success, False on error
*/
public function sasl_bind($authc, $pass, $authz=null)
{
if (!$this->conn) {
return false;
}
if (!function_exists('ldap_sasl_bind')) {
rcube::raise_error(array('code' => 100, 'type' => 'ldap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Unable to bind: ldap_sasl_bind() not exists"),
true);
return false;
}
if (!empty($authz)) {
$authz = 'u:' . $authz;
}
if (!empty($this->config['auth_method'])) {
$method = $this->config['auth_method'];
}
else {
$method = 'DIGEST-MD5';
}
$this->_debug("C: SASL Bind [mech: $method, authc: $authc, authz: $authz, pass: **** [" . strlen($pass) . "]");
if (ldap_sasl_bind($this->conn, NULL, $pass, $method, NULL, $authc, $authz)) {
$this->_debug("S: OK");
return true;
}
$this->_debug("S: ".ldap_error($this->conn));
rcube::raise_error(array(
'code' => ldap_errno($this->conn), 'type' => 'ldap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "SASL Bind failed for authcid=$authc ".ldap_error($this->conn)),
true);
return false;
}
/**
* Bind connection with DN and password
*
* @param string $dn Bind DN
* @param string $pass Bind password
*
* @return boolean True on success, False on error
*/
public function bind($dn, $pass)
{
if (!$this->conn) {
return false;
}
$this->_debug("C: Bind $dn, pass: **** [" . strlen($pass) . "]");
if (@ldap_bind($this->conn, $dn, $pass)) {
$this->_debug("S: OK");
return true;
}
$this->_debug("S: ".ldap_error($this->conn));
rcube::raise_error(array(
'code' => ldap_errno($this->conn), 'type' => 'ldap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
true);
return false;
}
/**
* Close connection to LDAP server
*/
public function close()
{
if ($this->conn) {
$this->_debug("C: Close");
ldap_unbind($this->conn);
$this->conn = null;
}
}
/**
* Return the last result set
*
* @return object rcube_ldap_result Result object
*/
function get_result()
{
return $this->result;
}
/**
* Get a specific LDAP entry, identified by its DN
*
* @param string $dn Record identifier
* @return array Hash array
*/
function get_entry($dn)
{
$rec = null;
if ($this->conn && $dn) {
$this->_debug("C: Read $dn [(objectclass=*)]");
if ($ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', $this->attributes)) {
$this->_debug("S: OK");
if ($entry = ldap_first_entry($this->conn, $ldap_result)) {
$rec = ldap_get_attributes($this->conn, $entry);
}
}
else {
$this->_debug("S: ".ldap_error($this->conn));
}
if (!empty($rec)) {
$rec['dn'] = $dn; // Add in the dn for the entry.
}
}
return $rec;
}
/**
* Execute the LDAP search based on the stored credentials
*
* @param string $base_dn The base DN to query
* @param string $filter The LDAP filter for search
* @param string $scope The LDAP scope (list|sub|base)
* @param array $attrs List of entry attributes to read
* @param array $prop Hash array with query configuration properties:
* - sort: array of sort attributes (has to be in sync with the VLV index)
* - search: search string used for VLV controls
* @param boolean $count_only Set to true if only entry count is requested
*
* @return mixed rcube_ldap_result object or number of entries (if count_only=true) or false on error
*/
public function search($base_dn, $filter = '', $scope = 'sub', $attrs = array('dn'), $prop = array(), $count_only = false)
{
if (!$this->conn) {
return false;
}
if (empty($filter)) {
$filter = '(objectclass=*)';
}
$this->_debug("C: Search $base_dn for $filter");
$function = self::scope2func($scope, $ns_function);
// find available VLV index for this query
if (!$count_only && ($vlv_sort = $this->_find_vlv($base_dn, $filter, $scope, $prop['sort']))) {
// when using VLV, we get the total count by...
// ...either reading numSubOrdinates attribute
if (($sub_filter = $this->config['numsub_filter']) &&
($result_count = @$ns_function($this->conn, $base_dn, $sub_filter, array('numSubOrdinates'), 0, 0, 0))
) {
$counts = ldap_get_entries($this->conn, $result_count);
for ($vlv_count = $j = 0; $j < $counts['count']; $j++)
$vlv_count += $counts[$j]['numsubordinates'][0];
$this->_debug("D: total numsubordinates = " . $vlv_count);
}
// ...or by fetching all records dn and count them
else if (!function_exists('ldap_parse_virtuallist_control')) {
$vlv_count = $this->search($base_dn, $filter, $scope, array('dn'), $prop, true);
}
$this->vlv_active = $this->_vlv_set_controls($vlv_sort, $this->list_page, $this->page_size, $prop['search']);
}
else {
$this->vlv_active = false;
}
// only fetch dn for count (should keep the payload low)
if ($ldap_result = @$function($this->conn, $base_dn, $filter,
$attrs, 0, (int)$this->config['sizelimit'], (int)$this->config['timelimit'])
) {
// when running on a patched PHP we can use the extended functions
// to retrieve the total count from the LDAP search result
if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
if (ldap_parse_result($this->conn, $ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) {
ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $vlv_count, $vresult);
$this->_debug("S: VLV result: last_offset=$last_offset; content_count=$vlv_count");
}
else {
$this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn)));
}
}
else if ($this->debug) {
$this->_debug("S: ".ldap_count_entries($this->conn, $ldap_result)." record(s) found");
}
$this->result = new rcube_ldap_result($this->conn, $ldap_result, $base_dn, $filter, $vlv_count);
return $count_only ? $this->result->count() : $this->result;
}
else {
$this->_debug("S: ".ldap_error($this->conn));
}
return false;
}
/**
* Modify an LDAP entry on the server
*
* @param string $dn Entry DN
* @param array $params Hash array of entry attributes
* @param int $mode Update mode (UPDATE_MOD_ADD | UPDATE_MOD_DELETE | UPDATE_MOD_REPLACE)
*/
public function modify($dn, $parms, $mode = 255)
{
// TODO: implement this
return false;
}
/**
* Wrapper for ldap_add()
*
* @see ldap_add()
*/
public function add($dn, $entry)
{
$this->_debug("C: Add $dn: ".print_r($entry, true));
$res = ldap_add($this->conn, $dn, $entry);
if ($res === false) {
$this->_debug("S: ".ldap_error($this->conn));
return false;
}
$this->_debug("S: OK");
return true;
}
/**
* Wrapper for ldap_delete()
*
* @see ldap_delete()
*/
public function delete($dn)
{
$this->_debug("C: Delete $dn");
$res = ldap_delete($this->conn, $dn);
if ($res === false) {
$this->_debug("S: ".ldap_error($this->conn));
return false;
}
$this->_debug("S: OK");
return true;
}
/**
* Wrapper for ldap_mod_replace()
*
* @see ldap_mod_replace()
*/
public function mod_replace($dn, $entry)
{
$this->_debug("C: Replace $dn: ".print_r($entry, true));
if (!ldap_mod_replace($this->conn, $dn, $entry)) {
$this->_debug("S: ".ldap_error($this->conn));
return false;
}
$this->_debug("S: OK");
return true;
}
/**
* Wrapper for ldap_mod_add()
*
* @see ldap_mod_add()
*/
public function mod_add($dn, $entry)
{
$this->_debug("C: Add $dn: ".print_r($entry, true));
if (!ldap_mod_add($this->conn, $dn, $entry)) {
$this->_debug("S: ".ldap_error($this->conn));
return false;
}
$this->_debug("S: OK");
return true;
}
/**
* Wrapper for ldap_mod_del()
*
* @see ldap_mod_del()
*/
public function mod_del($dn, $entry)
{
$this->_debug("C: Delete $dn: ".print_r($entry, true));
if (!ldap_mod_del($this->conn, $dn, $entry)) {
$this->_debug("S: ".ldap_error($this->conn));
return false;
}
$this->_debug("S: OK");
return true;
}
/**
* Wrapper for ldap_rename()
*
* @see ldap_rename()
*/
public function rename($dn, $newrdn, $newparent = null, $deleteoldrdn = true)
{
$this->_debug("C: Rename $dn to $newrdn");
if (!ldap_rename($this->conn, $dn, $newrdn, $newparent, $deleteoldrdn)) {
$this->_debug("S: ".ldap_error($this->conn));
return false;
}
$this->_debug("S: OK");
return true;
}
/**
* Wrapper for ldap_list() + ldap_get_entries()
*
* @see ldap_list()
* @see ldap_get_entries()
*/
public function list_entries($dn, $filter, $attributes = array('dn'))
{
$list = array();
$this->_debug("C: List $dn [{$filter}]");
if ($result = ldap_list($this->conn, $dn, $filter, $attributes)) {
$list = ldap_get_entries($this->conn, $result);
if ($list === false) {
$this->_debug("S: ".ldap_error($this->conn));
return array();
}
$count = $list['count'];
unset($list['count']);
$this->_debug("S: $count record(s)");
}
else {
$this->_debug("S: ".ldap_error($this->conn));
}
return $list;
}
/**
* Wrapper for ldap_read() + ldap_get_entries()
*
* @see ldap_read()
* @see ldap_get_entries()
*/
public function read_entries($dn, $filter, $attributes = null)
{
$this->_debug("C: Read $dn [{$filter}]");
if ($this->conn && $dn) {
if (!$attributes)
$attributes = $this->attributes;
$result = @ldap_read($this->conn, $dn, $filter, $attributes, 0, (int)$this->config['sizelimit'], (int)$this->config['timelimit']);
if ($result === false) {
$this->_debug("S: ".ldap_error($this->conn));
return false;
}
$this->_debug("S: OK");
return ldap_get_entries($this->conn, $result);
}
return false;
}
/**
* Choose the right PHP function according to scope property
*
* @param string $scope The LDAP scope (sub|base|list)
* @param string $ns_function Function to be used for numSubOrdinates queries
* @return string PHP function to be used to query directory
*/
public static function scope2func($scope, &$ns_function = null)
{
switch ($scope) {
case 'sub':
$function = $ns_function = 'ldap_search';
break;
case 'base':
$function = $ns_function = 'ldap_read';
break;
default:
$function = 'ldap_list';
$ns_function = 'ldap_read';
break;
}
return $function;
}
/**
* Convert the given scope integer value to a string representation
*/
public static function scopeint2str($scope)
{
switch ($scope) {
case 2: return 'sub';
case 1: return 'one';
case 0: return 'base';
default: $this->_debug("Scope $scope is not a valid scope integer");
}
return '';
}
/**
* Escapes the given value according to RFC 2254 so that it can be safely used in LDAP filters.
*
* @param string $val Value to quote
* @return string The escaped value
*/
public static function escape_value($val)
{
return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29',
'\\'=>'\5c', '/'=>'\2f'));
}
/**
* Escapes a DN value according to RFC 2253
*
* @param string $dn DN value o quote
* @return string The escaped value
*/
public static function escape_dn($dn)
{
return strtr($str, array(','=>'\2c', '='=>'\3d', '+'=>'\2b',
'<'=>'\3c', '>'=>'\3e', ';'=>'\3b', '\\'=>'\5c',
'"'=>'\22', '#'=>'\23'));
}
/**
* Normalize a LDAP result by converting entry attributes arrays into single values
*
* @param array $result LDAP result set fetched with ldap_get_entries()
* @return array Hash array with normalized entries, indexed by their DNs
*/
public static function normalize_result($result)
{
if (!is_array($result)) {
return array();
}
$entries = array();
for ($i = 0; $i < $result['count']; $i++) {
$key = $result[$i]['dn'] ? $result[$i]['dn'] : $i;
$entries[$key] = self::normalize_entry($result[$i]);
}
return $entries;
}
/**
* Turn an LDAP entry into a regular PHP array with attributes as keys.
*
* @param array $entry Attributes array as retrieved from ldap_get_attributes() or ldap_get_entries()
*
* @return array Hash array with attributes as keys
*/
public static function normalize_entry($entry)
{
if (!isset($entry['count'])) {
return $entry;
}
$rec = array();
for ($i=0; $i < $entry['count']; $i++) {
$attr = $entry[$i];
if ($entry[$attr]['count'] == 1) {
switch ($attr) {
case 'objectclass':
$rec[$attr] = array(strtolower($entry[$attr][0]));
break;
default:
$rec[$attr] = $entry[$attr][0];
break;
}
}
else {
for ($j=0; $j < $entry[$attr]['count']; $j++) {
$rec[$attr][$j] = $entry[$attr][$j];
}
}
}
return $rec;
}
/**
* Set server controls for Virtual List View (paginated listing)
*/
private function _vlv_set_controls($sort, $list_page, $page_size, $search = null)
{
$sort_ctrl = array('oid' => "1.2.840.113556.1.4.473", 'value' => self::_sort_ber_encode((array)$sort));
$vlv_ctrl = array('oid' => "2.16.840.1.113730.3.4.9", 'value' => self::_vlv_ber_encode(($offset = ($list_page-1) * $page_size + 1), $page_size, $search), 'iscritical' => true);
$this->_debug("C: Set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value'])) . " ($sort[0]);"
. " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size; $search)");
if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, array($sort_ctrl, $vlv_ctrl))) {
$this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SEARCH, 'vlvnotsupported');
return false;
}
return true;
}
/**
* Returns unified attribute name (resolving aliases)
*/
private static function _attr_name($namev)
{
// list of known attribute aliases
static $aliases = array(
'gn' => 'givenname',
'rfc822mailbox' => 'email',
'userid' => 'uid',
'emailaddress' => 'email',
'pkcs9email' => 'email',
);
list($name, $limit) = explode(':', $namev, 2);
$suffix = $limit ? ':'.$limit : '';
return (isset($aliases[$name]) ? $aliases[$name] : $name) . $suffix;
}
/**
* Quotes attribute value string
*
* @param string $str Attribute value
* @param bool $dn True if the attribute is a DN
*
* @return string Quoted string
*/
public static function quote_string($str, $dn=false)
{
// take firt entry if array given
if (is_array($str))
$str = reset($str);
if ($dn)
$replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c',
'>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', '"'=>'\22', '#'=>'\23');
else
$replace = array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c',
'/'=>'\2f');
return strtr($str, $replace);
}
/**
* Prints debug info to the log
*/
private function _debug($str)
{
if ($this->debug && class_exists('rcube')) {
rcube::write_log('ldap', $str);
}
}
/***************** Virtual List View (VLV) related utility functions **************** */
/**
* Return the search string value to be used in VLV controls
*/
private function _vlv_search($sort, $search)
{
foreach ($search as $attr => $value) {
if (!in_array(strtolower($attr), $sort)) {
$this->_debug("d: Cannot use VLV search using attribute not indexed: $attr (not in " . var_export($sort, true) . ")");
return null;
} else {
return $value;
}
}
}
/**
* Find a VLV index matching the given query attributes
*
* @return string Sort attribute or False if no match
*/
private function _find_vlv($base_dn, $filter, $scope, $sort_attrs = null)
{
if (!$this->config['vlv'] || $scope == 'base') {
return false;
}
// get vlv config
$vlv_config = $this->_read_vlv_config();
if ($vlv = $vlv_config[$base_dn]) {
$this->_debug("D: Found a VLV for $base_dn");
if ($vlv['filter'] == strtolower($filter) || stripos($filter, '(&'.$vlv['filter'].'(') === 0) {
$this->_debug("D: Filter matches");
if ($vlv['scope'] == $scope) {
// Not passing any sort attributes means you don't care
if (empty($sort_attrs) || in_array($sort_attrs, $vlv['sort'])) {
return $vlv['sort'][0];
}
}
else {
$this->_debug("D: Scope does not match");
}
}
else {
$this->_debug("D: Filter does not match");
}
}
else {
$this->_debug("D: No VLV for $base_dn");
}
return false;
}
/**
* Return VLV indexes and searches including necessary configuration
* details.
*/
private function _read_vlv_config()
{
if (empty($this->config['vlv']) || empty($this->config['config_root_dn'])) {
return array();
}
// return hard-coded VLV config
else if (is_array($this->config['vlv'])) {
return $this->config['vlv'];
}
// return cached result
if (is_array($this->vlv_config)) {
return $this->vlv_config;
}
if ($this->cache && ($cached_config = $this->cache->get('vlvconfig'))) {
$this->vlv_config = $cached_config;
return $this->vlv_config;
}
$this->vlv_config = array();
$ldap_result = ldap_search($this->conn, $this->config['config_root_dn'], '(objectclass=vlvsearch)', array('*'), 0, 0, 0);
$vlv_searches = new rcube_ldap_result($this->conn, $ldap_result, $this->config['config_root_dn'], '(objectclass=vlvsearch)');
if ($vlv_searches->count() < 1) {
$this->_debug("D: Empty result from search for '(objectclass=vlvsearch)' on '$config_root_dn'");
return array();
}
foreach ($vlv_searches->entries(true) as $vlv_search_dn => $vlv_search_attrs) {
// Multiple indexes may exist
$ldap_result = ldap_search($this->conn, $vlv_search_dn, '(objectclass=vlvindex)', array('*'), 0, 0, 0);
$vlv_indexes = new rcube_ldap_result($this->conn, $ldap_result, $vlv_search_dn, '(objectclass=vlvindex)');
// Reset this one for each VLV search.
$_vlv_sort = array();
foreach ($vlv_indexes->entries(true) as $vlv_index_dn => $vlv_index_attrs) {
$_vlv_sort[] = explode(' ', $vlv_index_attrs['vlvsort']);
}
$this->vlv_config[$vlv_search_attrs['vlvbase']] = array(
'scope' => self::scopeint2str($vlv_search_attrs['vlvscope']),
'filter' => strtolower($vlv_search_attrs['vlvfilter']),
'sort' => $_vlv_sort,
);
}
// cache this
if ($this->cache)
$this->cache->set('vlvconfig', $this->vlv_config);
$this->_debug("D: Refreshed VLV config: " . var_export($this->vlv_config, true));
return $this->vlv_config;
}
/**
* Generate BER encoded string for Virtual List View option
*
* @param integer List offset (first record)
* @param integer Records per page
*
* @return string BER encoded option value
*/
private static function _vlv_ber_encode($offset, $rpp, $search = '')
{
/*
this string is ber-encoded, php will prefix this value with:
04 (octet string) and 10 (length of 16 bytes)
the code behind this string is broken down as follows:
30 = ber sequence with a length of 0e (14) bytes following
02 = type integer (in two's complement form) with 2 bytes following (beforeCount): 01 00 (ie 0)
02 = type integer (in two's complement form) with 2 bytes following (afterCount): 01 18 (ie 25-1=24)
a0 = type context-specific/constructed with a length of 06 (6) bytes following
02 = type integer with 2 bytes following (offset): 01 01 (ie 1)
02 = type integer with 2 bytes following (contentCount): 01 00
with a search string present:
81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here)
81 indicates a user string is present where as a a0 indicates just a offset search
81 = type context-specific/constructed with a length of 06 (6) bytes following
The following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the
encoding of integer values (note: these values are in
two-complement form so since offset will never be negative bit 8 of the
leftmost octet should never by set to 1):
8.3.2: If the contents octets of an integer value encoding consist
of more than one octet, then the bits of the first octet (rightmost)
and bit 8 of the second (to the left of first octet) octet:
a) shall not all be ones; and
b) shall not all be zero
*/
if ($search) {
$search = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search);
$ber_val = self::_string2hex($search);
$str = self::_ber_addseq($ber_val, '81');
}
else {
// construct the string from right to left
$str = "020100"; # contentCount
$ber_val = self::_ber_encode_int($offset); // returns encoded integer value in hex format
// calculate octet length of $ber_val
$str = self::_ber_addseq($ber_val, '02') . $str;
// now compute length over $str
$str = self::_ber_addseq($str, 'a0');
}
// now tack on records per page
$str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str;
// now tack on sequence identifier and length
$str = self::_ber_addseq($str, '30');
return pack('H'.strlen($str), $str);
}
/**
* create ber encoding for sort control
*
* @param array List of cols to sort by
* @return string BER encoded option value
*/
private static function _sort_ber_encode($sortcols)
{
$str = '';
foreach (array_reverse((array)$sortcols) as $col) {
$ber_val = self::_string2hex($col);
// 30 = ber sequence with a length of octet value
// 04 = octet string with a length of the ascii value
$oct = self::_ber_addseq($ber_val, '04');
$str = self::_ber_addseq($oct, '30') . $str;
}
// now tack on sequence identifier and length
$str = self::_ber_addseq($str, '30');
return pack('H'.strlen($str), $str);
}
/**
* Add BER sequence with correct length and the given identifier
*/
private static function _ber_addseq($str, $identifier)
{
$len = dechex(strlen($str)/2);
if (strlen($len) % 2 != 0)
$len = '0'.$len;
return $identifier . $len . $str;
}
/**
* Returns BER encoded integer value in hex format
*/
private static function _ber_encode_int($offset)
{
$val = dechex($offset);
$prefix = '';
// check if bit 8 of high byte is 1
if (preg_match('/^[89abcdef]/', $val))
$prefix = '00';
if (strlen($val)%2 != 0)
$prefix .= '0';
return $prefix . $val;
}
/**
* Returns ascii string encoded in hex
*/
private static function _string2hex($str)
{
$hex = '';
for ($i=0; $i < strlen($str); $i++) {
$hex .= dechex(ord($str[$i]));
}
return $hex;
}
}
diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc
index 94556f96b..7451f433b 100644
--- a/program/steps/addressbook/save.inc
+++ b/program/steps/addressbook/save.inc
@@ -1,258 +1,262 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/addressbook/save.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2013, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Save a contact entry or to add a new one |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
$CONTACTS = rcmail_contact_source(null, true, true);
$cid = rcube_utils::get_input_value('_cid', rcube_utils::INPUT_POST);
$return_action = empty($cid) ? 'add' : 'edit';
// Source changed, display the form again
if (!empty($_GET['_reload'])) {
$RCMAIL->overwrite_action($return_action);
return;
}
// cannot edit record
if ($CONTACTS->readonly) {
$OUTPUT->show_message('contactreadonly', 'error');
$RCMAIL->overwrite_action($return_action);
return;
}
// read POST values into hash array
$a_record = array();
foreach ($GLOBALS['CONTACT_COLTYPES'] as $col => $colprop) {
if ($colprop['composite']) {
continue;
}
$fname = '_'.$col;
// gather form data of composite fields
if ($colprop['childs']) {
$values = array();
foreach ($colprop['childs'] as $childcol => $cp) {
$vals = rcube_utils::get_input_value('_'.$childcol, rcube_utils::INPUT_POST, true);
foreach ((array)$vals as $i => $val) {
$values[$i][$childcol] = $val;
}
}
$subtypes = isset($_REQUEST['_subtype_' . $col]) ? (array)rcube_utils::get_input_value('_subtype_' . $col, rcube_utils::INPUT_POST) : array('');
foreach ($subtypes as $i => $subtype) {
$suffix = $subtype ? ':'.$subtype : '';
if ($values[$i]) {
$a_record[$col.$suffix][] = $values[$i];
}
}
}
// assign values and subtypes
else if (is_array($_POST[$fname])) {
$values = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
$subtypes = rcube_utils::get_input_value('_subtype_' . $col, rcube_utils::INPUT_POST);
foreach ($values as $i => $val) {
if ($col == 'email') {
// extract email from full address specification, e.g. "Name" <addr@domain.tld>
$addr = rcube_mime::decode_address_list($val, 1, false);
if (!empty($addr) && ($addr = array_pop($addr)) && $addr['mailto']) {
$val = $addr['mailto'];
}
}
$subtype = $subtypes[$i] ? ':'.$subtypes[$i] : '';
$a_record[$col.$subtype][] = $val;
}
}
else if (isset($_POST[$fname])) {
$a_record[$col] = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
// normalize the submitted date strings
if ($colprop['type'] == 'date') {
if ($a_record[$col] && ($dt = rcube_utils::anytodatetime($a_record[$col]))) {
$a_record[$col] = $dt->format('Y-m-d');
}
else {
unset($a_record[$col]);
}
}
}
}
// Generate contact's display name (must be before validation)
if (empty($a_record['name'])) {
$a_record['name'] = rcube_addressbook::compose_display_name($a_record, true);
// Reset it if equals to email address (from compose_display_name())
$email = rcube_addressbook::get_col_values('email', $a_record, true);
if ($a_record['name'] == $email[0]) {
$a_record['name'] = '';
}
}
// do input checks (delegated to $CONTACTS instance)
if (!$CONTACTS->validate($a_record)) {
$err = (array)$CONTACTS->get_error();
$OUTPUT->show_message($err['message'] ? rcube::Q($err['message']) : 'formincomplete', 'warning');
$GLOBALS['EDIT_RECORD'] = $a_record; // store submitted data to be used in edit form
$RCMAIL->overwrite_action($return_action);
return;
}
// get raw photo data if changed
if (isset($a_record['photo'])) {
if ($a_record['photo'] == '-del-') {
$a_record['photo'] = '';
}
else if ($tempfile = $_SESSION['contacts']['files'][$a_record['photo']]) {
$tempfile = $RCMAIL->plugins->exec_hook('attachment_get', $tempfile);
if ($tempfile['status'])
$a_record['photo'] = $tempfile['data'] ? $tempfile['data'] : @file_get_contents($tempfile['path']);
}
else
unset($a_record['photo']);
// cleanup session data
$RCMAIL->plugins->exec_hook('attachments_cleanup', array('group' => 'contact'));
$RCMAIL->session->remove('contacts');
}
$source = rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC);
// update an existing contact
if (!empty($cid)) {
$plugin = $RCMAIL->plugins->exec_hook('contact_update',
array('id' => $cid, 'record' => $a_record, 'source' => $source));
$a_record = $plugin['record'];
if (!$plugin['abort'])
$result = $CONTACTS->update($cid, $a_record);
else
$result = $plugin['result'];
if ($result) {
// LDAP DN change
if (is_string($result) && strlen($result)>1) {
$newcid = $result;
// change cid in POST for 'show' action
$_POST['_cid'] = $newcid;
}
// define list of cols to be displayed
$a_js_cols = array();
$record = $CONTACTS->get_record($newcid ? $newcid : $cid, true);
$record['email'] = reset($CONTACTS->get_col_values('email', $record, true));
$record['name'] = rcube_addressbook::compose_list_name($record);
foreach (array('name') as $col) {
$a_js_cols[] = rcube::Q((string)$record[$col]);
}
+ // performance: unset some big data items we don't need here
+ $record = array_intersect_key($record, array('ID' => 1,'email' => 1,'name' => 1));
+ $record['_type'] = 'person';
+
// update the changed col in list
$OUTPUT->command('parent.update_contact_row', $cid, $a_js_cols, $newcid, $source, $record);
// show confirmation
$OUTPUT->show_message('successfullysaved', 'confirmation', null, false);
$RCMAIL->overwrite_action('show');
}
else {
// show error message
$err = $CONTACTS->get_error();
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false);
$RCMAIL->overwrite_action('show');
}
}
// insert a new contact
else {
// Name of the addressbook already selected on the list
$orig_source = rcube_utils::get_input_value('_orig_source', rcube_utils::INPUT_GPC);
if (!strlen($source)) {
$source = $orig_source;
}
// show notice if existing contacts with same e-mail are found
foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) {
if ($email && ($res = $CONTACTS->search('email', $email, 1, false, true)) && $res->count) {
$OUTPUT->show_message('contactexists', 'notice', null, false);
break;
}
}
$plugin = $RCMAIL->plugins->exec_hook('contact_create', array(
'record' => $a_record, 'source' => $source));
$a_record = $plugin['record'];
// insert record and send response
if (!$plugin['abort'])
$insert_id = $CONTACTS->insert($a_record);
else
$insert_id = $plugin['result'];
if ($insert_id) {
$CONTACTS->reset();
// add new contact to the specified group
if ($CONTACTS->groups && $CONTACTS->group_id) {
$plugin = $RCMAIL->plugins->exec_hook('group_addmembers', array(
'group_id' => $CONTACTS->group_id, 'ids' => $insert_id, 'source' => $source));
$counts = $CONTACTS->count();
if (!$plugin['abort']) {
if (($maxnum = $RCMAIL->config->get('max_group_members', 0)) && ($counts->count + 1 > $maxnum))
$OUTPUT->show_message('maxgroupmembersreached', 'warning', array('max' => $maxnum));
$CONTACTS->add_to_group($plugin['group_id'], $plugin['ids']);
}
}
else {
$counts = $CONTACTS->count();
}
if ((string)$source === (string)$orig_source) {
// add contact row or jump to the page where it should appear
$CONTACTS->reset();
$result = $CONTACTS->search($CONTACTS->primary_key, $insert_id);
rcmail_js_contacts_list($result, 'parent.');
$OUTPUT->command('parent.contact_list.select', rcube_utils::html_identifier($insert_id));
// update record count display
$CONTACTS->reset();
$OUTPUT->command('parent.set_rowcount', rcmail_get_rowcount_text($counts));
}
else {
// re-set iframe
$OUTPUT->command('parent.show_contentframe');
}
// show confirmation
$OUTPUT->show_message('successfullysaved', 'confirmation', null, false);
$OUTPUT->send('iframe');
}
else {
// show error message
$err = $CONTACTS->get_error();
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false);
$RCMAIL->overwrite_action('add');
}
}
diff --git a/program/steps/mail/check_recent.inc b/program/steps/mail/check_recent.inc
index cfdcda605..70f4c03a6 100644
--- a/program/steps/mail/check_recent.inc
+++ b/program/steps/mail/check_recent.inc
@@ -1,156 +1,156 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/check_recent.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Check for recent messages, in all mailboxes |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
// If there's no folder or messages list, there's nothing to update
// This can happen on 'refresh' request
if (empty($_POST['_folderlist']) && empty($_POST['_list'])) {
return;
}
$trash = $RCMAIL->config->get('trash_mbox');
$current = $RCMAIL->storage->get_folder();
$check_all = $RCMAIL->action != 'refresh' || (bool)$RCMAIL->config->get('check_all_folders');
$search_request = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC);
if ($search_request && $_SESSION['search_request'] != $search_request) {
$search_request = null;
}
// list of folders to check
if ($check_all) {
$a_mailboxes = $RCMAIL->storage->list_folders_subscribed('', '*', 'mail');
}
else if ($search_request && is_object($_SESSION['search'][1])) {
$a_mailboxes = (array) $_SESSION['search'][1]->get_parameters('MAILBOX');
}
else {
$a_mailboxes = (array) $current;
if ($current != 'INBOX') {
$a_mailboxes[] = 'INBOX';
}
}
// Control folders list from a plugin
$plugin = $RCMAIL->plugins->exec_hook('check_recent', array('folders' => $a_mailboxes, 'all' => $check_all));
$a_mailboxes = $plugin['folders'];
// check recent/unseen counts
foreach ($a_mailboxes as $mbox_name) {
$is_current = $mbox_name == $current || ($search_request && is_object($_SESSION['search'][1]) && in_array($mbox_name, (array)$_SESSION['search'][1]->get_parameters('MAILBOX')));
if ($is_current) {
// Synchronize mailbox cache, handle flag changes
$RCMAIL->storage->folder_sync($mbox_name);
}
// Get mailbox status
$status = $RCMAIL->storage->folder_status($mbox_name, $diff);
if ($status & 1) {
// trigger plugin hook
$RCMAIL->plugins->exec_hook('new_messages',
array('mailbox' => $mbox_name, 'is_current' => $is_current, 'diff' => $diff));
}
rcmail_send_unread_count($mbox_name, true, null,
(!$is_current && ($status & 1)) ? 'recent' : '');
if ($status && $is_current) {
// refresh saved search set
if ($search_request && isset($_SESSION['search'])) {
unset($search_request); // only do this once
$_SESSION['search'] = $RCMAIL->storage->refresh_search();
if ($_SESSION['search'][1]->multi)
$mbox_name = '';
}
if (!empty($_POST['_quota'])) {
$OUTPUT->command('set_quota', $RCMAIL->quota_content());
}
- $OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS'));
+ $OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS', true));
// "No-list" mode, don't get messages
if (empty($_POST['_list'])) {
continue;
}
// get overall message count; allow caching because rcube_storage::folder_status()
// did a refresh but only in list mode
$list_mode = $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL';
$all_count = $RCMAIL->storage->count($mbox_name, $list_mode, $list_mode == 'THREADS', false);
$page = $RCMAIL->storage->get_page();
$page_size = $RCMAIL->storage->get_pagesize();
// check current page if we're not on the first page
if ($all_count && $page > 1) {
$remaining = $all_count - $page_size * ($page - 1);
if ($remaining <= 0) {
$page -= 1;
$RCMAIL->storage->set_page($page);
$_SESSION['page'] = $page;
}
}
$OUTPUT->set_env('messagecount', $all_count);
$OUTPUT->set_env('pagecount', ceil($all_count/$page_size));
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($all_count), $mbox_name);
$OUTPUT->set_env('current_page', $all_count ? $page : 1);
// remove old rows (and clear selection if new list is empty)
$OUTPUT->command('message_list.clear', $all_count ? false : true);
if ($all_count) {
$a_headers = $RCMAIL->storage->list_messages($mbox_name, null, rcmail_sort_column(), rcmail_sort_order());
// add message rows
rcmail_js_message_list($a_headers, false);
// remove messages that don't exists from list selection array
$OUTPUT->command('update_selection');
}
}
// handle flag updates
else if ($is_current && ($uids = rcube_utils::get_input_value('_uids', rcube_utils::INPUT_GPC)) && empty($search_request)) {
$data = $RCMAIL->storage->folder_data($mbox_name);
if (empty($_SESSION['list_mod_seq']) || $_SESSION['list_mod_seq'] != $data['HIGHESTMODSEQ']) {
$flags = $RCMAIL->storage->list_flags($mbox_name, explode(',', $uids), $_SESSION['list_mod_seq']);
foreach ($flags as $idx => $row) {
$flags[$idx] = array_change_key_case(array_map('intval', $row));
}
// remember last HIGHESTMODSEQ value (if supported)
if (!empty($data['HIGHESTMODSEQ'])) {
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
}
$RCMAIL->output->set_env('recent_flags', $flags);
}
}
// set trash folder state
if ($mbox_name === $trash) {
- $OUTPUT->command('set_trash_count', $RCMAIL->storage->count($mbox_name, 'EXISTS'));
+ $OUTPUT->command('set_trash_count', $RCMAIL->storage->count($mbox_name, 'EXISTS', true));
}
}
// trigger refresh hook
$RCMAIL->plugins->exec_hook('refresh', array());
$OUTPUT->send();
diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc
index c4a6df57b..929dda299 100644
--- a/program/steps/mail/list.inc
+++ b/program/steps/mail/list.inc
@@ -1,142 +1,142 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/list.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Send message list to client (as remote response) |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
if (!$OUTPUT->ajax_call) {
return;
}
$save_arr = array();
$dont_override = (array) $RCMAIL->config->get('dont_override');
// is there a sort type for this request?
if ($sort = rcube_utils::get_input_value('_sort', rcube_utils::INPUT_GET)) {
// yes, so set the sort vars
list($sort_col, $sort_order) = explode('_', $sort);
// set session vars for sort (so next page and task switch know how to sort)
if (!in_array('message_sort_col', $dont_override)) {
$_SESSION['sort_col'] = $save_arr['message_sort_col'] = $sort_col;
}
if (!in_array('message_sort_order', $dont_override)) {
$_SESSION['sort_order'] = $save_arr['message_sort_order'] = $sort_order;
}
}
// is there a set of columns for this request?
if ($cols = rcube_utils::get_input_value('_cols', rcube_utils::INPUT_GET)) {
$_SESSION['list_attrib']['columns'] = explode(',', $cols);
if (!in_array('list_cols', $dont_override)) {
$save_arr['list_cols'] = explode(',', $cols);
}
}
if (!empty($save_arr)) {
$RCMAIL->user->save_prefs($save_arr);
}
$mbox_name = $RCMAIL->storage->get_folder();
$threading = (bool) $RCMAIL->storage->get_threading();
// Synchronize mailbox cache, handle flag changes
$RCMAIL->storage->folder_sync($mbox_name);
// initialize searching result if search_filter is used
if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') {
$search_request = md5($mbox_name.$_SESSION['search_scope'].$_SESSION['search_filter']);
$RCMAIL->storage->search($mbox_name, $_SESSION['search_filter'], RCUBE_CHARSET, rcmail_sort_column());
$_SESSION['search'] = $RCMAIL->storage->get_search_set();
$_SESSION['search_request'] = $search_request;
$OUTPUT->set_env('search_request', $search_request);
$OUTPUT->set_env('search_filter', $_SESSION['search_filter']);
}
// fetch message headers
if ($count = $RCMAIL->storage->count($mbox_name, $threading ? 'THREADS' : 'ALL', !empty($_REQUEST['_refresh'])))
$a_headers = $RCMAIL->storage->list_messages($mbox_name, NULL, rcmail_sort_column(), rcmail_sort_order());
// update search set (possible change of threading mode)
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'])
&& $_SESSION['search_request'] == $_REQUEST['_search']
) {
$_SESSION['search'] = $RCMAIL->storage->get_search_set();
}
// remove old search data
else if (empty($_REQUEST['_search']) && isset($_SESSION['search'])) {
$RCMAIL->session->remove('search');
}
// empty result? we'll skip UNSEEN counting in rcmail_send_unread_count()
if (empty($search_request) && empty($a_headers)) {
$unseen = 0;
}
// update mailboxlist
rcmail_send_unread_count($mbox_name, !empty($_REQUEST['_refresh']), $unseen);
// update message count display
$pages = ceil($count/$RCMAIL->storage->get_pagesize());
-$exists = $RCMAIL->storage->count($mbox_name, 'EXISTS');
+$exists = $RCMAIL->storage->count($mbox_name, 'EXISTS', true);
$OUTPUT->set_env('messagecount', $count);
$OUTPUT->set_env('pagecount', $pages);
$OUTPUT->set_env('threading', $threading);
$OUTPUT->set_env('current_page', $count ? $RCMAIL->storage->get_page() : 1);
$OUTPUT->set_env('exists', $exists);
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count), $mbox_name);
// remove old message rows if commanded by the client
if (!empty($_REQUEST['_clear'])) {
$OUTPUT->command('clear_message_list');
}
// add message rows
rcmail_js_message_list($a_headers, false, $cols);
if (isset($a_headers) && count($a_headers)) {
if ($search_request) {
$OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count));
}
// remember last HIGHESTMODSEQ value (if supported)
// we need it for flag updates in check-recent
$data = $RCMAIL->storage->folder_data($mbox_name);
if (!empty($data['HIGHESTMODSEQ'])) {
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
}
}
else {
// handle IMAP errors (e.g. #1486905)
if ($err_code = $RCMAIL->storage->get_error_code()) {
$RCMAIL->display_server_error();
}
else if ($search_request)
$OUTPUT->show_message('searchnomatch', 'notice');
else
$OUTPUT->show_message('nomessagesfound', 'notice');
}
// set trash folder state
if ($mbox_name === $RCMAIL->config->get('trash_mbox')) {
$OUTPUT->command('set_trash_count', $exists);
}
// send response
$OUTPUT->send();
diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc
index c29985875..d98d49d1f 100644
--- a/program/steps/mail/move_del.inc
+++ b/program/steps/mail/move_del.inc
@@ -1,174 +1,174 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/move_del.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Move the submitted messages to a specific mailbox or delete them |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
// only process ajax requests
if (!$OUTPUT->ajax_call)
return;
// count messages before changing anything
$threading = (bool) $RCMAIL->storage->get_threading();
$old_count = $RCMAIL->storage->count(NULL, $threading ? 'THREADS' : 'ALL');
$old_pages = ceil($old_count / $RCMAIL->storage->get_pagesize());
$trash = $RCMAIL->config->get('trash_mbox');
// move messages
if ($RCMAIL->action == 'move' && !empty($_POST['_uid']) && strlen($_POST['_target_mbox'])) {
$target = rcube_utils::get_input_value('_target_mbox', rcube_utils::INPUT_POST, true);
$trash = $RCMAIL->config->get('trash_mbox');
$success = true;
foreach (rcmail::get_uids() as $mbox => $uids) {
if ($mbox === $target) {
$count += count($uids);
}
else if ($RCMAIL->storage->move_message($uids, $target, $mbox)) {
$count += count($uids);
}
else {
$success = false;
}
}
if (!$success) {
// send error message
if ($_POST['_from'] != 'show')
$OUTPUT->command('list_mailbox');
$RCMAIL->display_server_error('errormoving', null, $target == $trash ? 'delete' : '');
$OUTPUT->send();
exit;
}
else {
$OUTPUT->show_message('messagemoved', 'confirmation');
}
if (!empty($_POST['_refresh'])) {
// FIXME: send updated message rows instead of releading the entire list
$OUTPUT->command('refresh_list');
}
else {
$addrows = true;
}
}
// delete messages
else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) {
foreach (rcmail::get_uids() as $mbox => $uids) {
$del += (int)$RCMAIL->storage->delete_message($uids, $mbox);
$count += count($uids);
}
if (!$del) {
// send error message
if ($_POST['_from'] != 'show')
$OUTPUT->command('list_mailbox');
$RCMAIL->display_server_error('errordeleting');
$OUTPUT->send();
exit;
}
else {
$OUTPUT->show_message('messagedeleted', 'confirmation');
}
$addrows = true;
}
// unknown action or missing query param
else {
$OUTPUT->show_message('internalerror', 'error');
$OUTPUT->send();
exit;
}
$search_request = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC);
// refresh saved search set after moving some messages
if ($search_request && $RCMAIL->storage->get_search_set()) {
$_SESSION['search'] = $RCMAIL->storage->refresh_search();
}
if ($_POST['_from'] == 'show') {
if ($next = rcube_utils::get_input_value('_next_uid', rcube_utils::INPUT_GPC))
$OUTPUT->command('show_message', $next);
else
$OUTPUT->command('command', 'list');
}
else {
$msg_count = $RCMAIL->storage->count(NULL, $threading ? 'THREADS' : 'ALL');
$exists = $RCMAIL->storage->count($mbox, 'EXISTS', true);
$page_size = $RCMAIL->storage->get_pagesize();
$page = $RCMAIL->storage->get_page();
$pages = ceil($msg_count / $page_size);
$nextpage_count = $old_count - $page_size * $page;
$remaining = $msg_count - $page_size * ($page - 1);
// jump back one page (user removed the whole last page)
if ($page > 1 && $remaining == 0) {
$page -= 1;
$RCMAIL->storage->set_page($page);
$_SESSION['page'] = $page;
$jump_back = true;
}
// update message count display
$OUTPUT->set_env('messagecount', $msg_count);
$OUTPUT->set_env('current_page', $page);
$OUTPUT->set_env('pagecount', $pages);
$OUTPUT->set_env('exists', $exists);
// update mailboxlist
$mbox = $RCMAIL->storage->get_folder();
$unseen_count = $msg_count ? $RCMAIL->storage->count($mbox, 'UNSEEN') : 0;
$old_unseen = rcmail_get_unseen_count($mbox);
if ($old_unseen != $unseen_count) {
$OUTPUT->command('set_unread_count', $mbox, $unseen_count, ($mbox == 'INBOX'));
rcmail_set_unseen_count($mbox, $unseen_count);
}
if ($RCMAIL->action == 'move' && strlen($target)) {
rcmail_send_unread_count($target, true);
}
$OUTPUT->command('set_quota', $RCMAIL->quota_content());
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count), $mbox);
if ($threading) {
$count = rcube_utils::get_input_value('_count', rcube_utils::INPUT_POST);
}
// add new rows from next page (if any)
if ($addrows && $count && $uids != '*' && ($jump_back || $nextpage_count > 0)) {
$a_headers = $RCMAIL->storage->list_messages($mbox, NULL,
rcmail_sort_column(), rcmail_sort_order(), $jump_back ? NULL : $count);
rcmail_js_message_list($a_headers, false);
}
// set trash folder state
if ($mbox === $trash) {
$OUTPUT->command('set_trash_count', $exists);
}
else if ($target !== null && $target === $trash) {
- $OUTPUT->command('set_trash_count', $RCMAIL->storage->count($trash, 'EXISTS'));
+ $OUTPUT->command('set_trash_count', $RCMAIL->storage->count($trash, 'EXISTS', true));
}
}
// send response
$OUTPUT->send();
diff --git a/skins/larry/styles.css b/skins/larry/styles.css
index 1baca0963..580b20f1a 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -1,2703 +1,2703 @@
/**
* Roundcube webmail styles for skin "Larry"
*
* Copyright (c) 2012, The Roundcube Dev Team
* Screendesign by FLINT / Büro für Gestaltung, bueroflint.com
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
body {
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 11px;
color: #333;
background: url(images/linen.jpg) repeat #d1d5d8;
margin: 0;
}
body.noscroll {
/* also avoids bounce effect in Chrome and Safari */
overflow: hidden;
}
a {
color: #0069a6;
}
a:visited {
color: #0186ba;
}
img {
border: 0;
}
input[type="text"],
input[type="password"],
textarea {
margin: 0; /* Safari by default adds a margin */
padding: 4px;
border: 1px solid #b2b2b2;
border-radius: 4px;
box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
-moz-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
-webkit-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
-o-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="text"]:required,
input[type="password"]:required,
input.button:focus,
textarea:focus {
border-color: #4787b1;
box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
-o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
outline: none;
}
input.placeholder,
textarea.placeholder {
color: #aaa;
}
.bold {
font-weight: bold;
}
/* fixes vertical alignment of checkboxes and labels */
label input,
label span {
vertical-align: middle;
}
/*** buttons ***/
input.button {
display: inline-block;
margin: 0 2px;
padding: 2px 5px;
color: #525252;
text-shadow: 0px 1px 1px #fff;
border: 1px solid #c0c0c0;
border-radius: 4px;
background: #f7f7f7;
background: -moz-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f9f9f9), color-stop(100%,#e6e6e6));
background: -o-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: -ms-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-o-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-webkit-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-moz-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
text-decoration: none;
outline: none;
}
.formbuttons input.button {
color: #ddd;
font-size: 110%;
text-shadow: 0px 1px 1px #333;
padding: 4px 12px;
border-color: #465864;
border-radius: 5px;
background: #7a7b7d;
background: -moz-linear-gradient(top, #7b7b7b 0%, #606060 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#7b7b7b), color-stop(100%,#606060)); /* Chrome,Safari4+ */
background: -o-linear-gradient(top, #7b7b7b 0%, #606060 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #7b7b7b 0%, #606060 100%); /* IE10+ */
background: linear-gradient(top, #7b7b7b 0%, #606060 100%); /* W3C */
box-shadow: 0 1px 1px 0 #ccc, inset 0 1px 0 0 #888;
-o-box-shadow: 0 1px 1px 0 #ccc, inset 0 1px 0 0 #888;
-webkit-box-shadow: 0 1px 1px 0 #ccc, inset 0 1px 0 0 #888;
-moz-box-shadow: 0 1px 1px 0 #ccc, inset 0 1px 0 0 #888;
}
.formbuttons input.button:hover,
.formbuttons input.button:focus,
input.button.mainaction:hover,
input.button.mainaction:focus {
color: #f2f2f2;
border-color: #465864;
box-shadow: 0 0 5px 2px rgba(71,135,177, 0.6), inset 0 1px 0 0 #888;
-moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.6), inset 0 1px 0 0 #888;
-webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.6), inset 0 1px 0 0 #888;
-o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.6), inset 0 1px 0 0 #888;
}
.formbuttons input.button:active {
color: #fff;
background: -moz-linear-gradient(top, #5c5c5c 0%, #7b7b7b 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#5c5c5c), color-stop(100%,#7b7b7b));
background: -o-linear-gradient(top, #5c5c5c 0%, #7b7b7b 100%);
background: -ms-linear-gradient(top, #5c5c5c 0%, #7b7b7b 100%);
background: linear-gradient(top, #5c5c5c 0%, #7b7b7b 100%);
}
input.button.mainaction {
color: #ededed;
text-shadow: 0px 1px 1px #333;
border-color: #1f262c;
background: #505050;
background: -moz-linear-gradient(top, #505050 0%, #2a2e31 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#505050), color-stop(100%,#2a2e31));
background: -o-linear-gradient(top, #505050 0%, #2a2e31 100%);
background: -ms-linear-gradient(top, #505050 0%, #2a2e31 100%);
background: linear-gradient(top, #505050 0%, #2a2e31 100%);
box-shadow: inset 0 1px 0 0 #777;
-moz-box-shadow: inset 0 1px 0 0 #777;
-webkit-box-shadow: inset 0 1px 0 0 #777;
-o-box-shadow: inset 0 1px 0 0 #777;
}
input.button.mainaction:active {
color: #fff;
background: #515151;
background: -moz-linear-gradient(top, #2a2e31 0%, #505050 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#2a2e31), color-stop(100%,#505050));
background: -o-linear-gradient(top, #2a2e31 0%, #505050 100%);
background: -ms-linear-gradient(top, #2a2e31 0%, #505050 100%);
background: linear-gradient(top, #2a2e31 0%, #505050 100%);
}
input.button[disabled],
input.button[disabled]:hover,
input.button.mainaction[disabled] {
color: #aaa !important;
}
input.mainaction {
font-weight: bold;
}
/** link buttons **/
a.button,
.buttongroup {
display: inline-block;
margin: 0 2px;
padding: 2px 5px;
color: #525252;
text-shadow: 0px 1px 1px #fff;
border: 1px solid #c6c6c6;
border-radius: 4px;
background: #f7f7f7;
background: -moz-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f9f9f9), color-stop(100%,#e6e6e6));
background: -o-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: -ms-linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
background: linear-gradient(top, #f9f9f9 0%, #e6e6e6 100%);
box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-o-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-webkit-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
-moz-box-shadow: 0 1px 1px 0 rgba(140, 140, 140, 0.3);
text-decoration: none;
}
.buttongroup {
padding: 0;
white-space: nowrap;
}
a.button:focus,
input.button:focus {
border-color: #4fadd5;
box-shadow: 0 0 2px 1px rgba(71,135,177, 0.6);
-moz-box-shadow: 0 0 2px 1px rgba(71,135,177, 0.6);
-webkit-box-shadow: 0 0 2px 1px rgba(71,135,177, 0.6);
-o-box-shadow: 0 0 2px 1px rgba(71,135,177, 0.6);
outline: none;
}
label.disabled,
a.button.disabled {
color: #999;
}
a.button.disabled,
input.button.disabled,
input.button[disabled],
a.button.disabled:hover,
input.button.disabled:hover,
input.button[disabled]:hover {
border-color: #c6c6c6;
box-shadow: 0 1px 1px 0 rgba(160, 160, 160, 0.4);
-o-box-shadow: 0 1px 1px 0 rgba(160, 160, 160, 0.4);
-webkit-box-shadow: 0 1px 1px 0 rgba(160, 160, 160, 0.4);
-moz-box-shadow: 0 1px 1px 0 rgba(160, 160, 160, 0.4);
}
a.button.disabled span.inner {
opacity: 0.4;
filter: alpha(opacity=40);
}
.buttongroup a.button {
margin: 0;
border-width: 0 1px 0 0;
border-radius: 0;
background: none;
box-shadow: none;
-o-box-shadow: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
}
.buttongroup a.button.first,
.buttongroup a.button:first-child {
border-radius: 4px 0 0 4px;
border-left: 0;
}
.buttongroup a.button.last,
.buttongroup a.button:last-child {
border-radius: 0 4px 4px 0;
border-right: 0;
}
a.button.pressed,
a.button:active,
input.button:active {
background: #e6e6e6;
background: -moz-linear-gradient(top, #e6e6e6 0%, #f9f9f9 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e6e6e6), color-stop(100%,#f9f9f9));
background: -o-linear-gradient(top, #e6e6e6 0%, #f9f9f9 100%);
background: -ms-linear-gradient(top, #e6e6e6 0%, #f9f9f9 100%);
background: linear-gradient(top, #e6e6e6 0%, #f9f9f9 100%);
}
.pagenav.dark a.button {
font-weight: bold;
border-color: #e6e6e6;
background: #d8d8d8;
background: -moz-linear-gradient(top, #d8d8d8 0%, #bababa 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#d8d8d8), color-stop(100%,#bababa));
background: -o-linear-gradient(top, #d8d8d8 0%, #bababa 100%);
background: -ms-linear-gradient(top, #d8d8d8 0%, #bababa 100%);
background: linear-gradient(top, #d8d8d8 0%, #bababa 100%);
box-shadow: 0 1px 1px 0 #999;
-o-box-shadow: 0 1px 1px 0 #999;
-webkit-box-shadow: 0 1px 1px 0 #999;
-moz-box-shadow: 0 1px 1px 0 #999;
}
.pagenav.dark a.button.pressed {
background: #bababa;
background: -moz-linear-gradient(top, #bababa 0%, #d8d8d8 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#bababa), color-stop(100%,#d8d8d8));
background: -o-linear-gradient(top, #bababa 0%, #d8d8d8 100%);
background: -ms-linear-gradient(top, #bababa 0%, #d8d8d8 100%);
background: linear-gradient(top, #bababa 0%, #d8d8d8 100%);
}
.buttongroup a.button.selected,
.buttongroup a.button.selected:hover {
background: #8a8a8a;
background: -moz-linear-gradient(top, #909090 0%, #858585 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#909090), color-stop(100%,#858585));
background: -o-linear-gradient(top, #909090 0%, #858585 100%);
background: -ms-linear-gradient(top, #909090 0%, #858585 100%);
background: linear-gradient(top, #909090 0%, #858585 100%);
-webkit-box-shadow: inset 0 1px 2px 0 #555;
-moz-box-shadow: inset 0 1px 2px 0 #555;
box-shadow: inset 0 1px 2px 0 #555;
border-right-color: #555;
border-left-color: #555;
}
.pagenav a.button {
padding: 1px 3px;
height: 16px;
vertical-align: middle;
margin-bottom: 1px;
}
.pagenav .buttongroup a.button,
.pagenav .buttongroup a.button:hover {
padding: 1px 5px;
margin-bottom: 0;
}
a.button span.icon,
.pagenav a.button span.inner {
display: inline-block;
width: 16px;
height: 13px;
text-indent: 1000px;
overflow: hidden;
background: url(images/buttons.png) -6px -211px no-repeat;
}
a.button.prevpage span.icon,
.pagenav a.prevpage span.inner {
background-position: -7px -226px;
}
a.button.nextpage span.icon,
.pagenav a.nextpage span.inner {
background-position: -28px -226px;
}
a.button.lastpage span.icon,
.pagenav a.lastpage span.inner {
background-position: -28px -211px;
}
a.button.pageup span.icon,
.pagenav a.pageup span.inner {
background-position: -7px -241px;
}
a.button.pagedown span.icon,
.pagenav a.pagedown span.inner {
background-position: -29px -241px;
}
a.button.reply span.icon,
.pagenav a.reply span.inner {
background-position: -7px -256px;
}
a.button.forward span.icon,
.pagenav a.forward span.inner {
background-position: -29px -256px;
}
a.button.replyall span.icon,
.pagenav a.replyall span.inner {
background-position: -7px -271px;
}
a.button.extwin span.icon,
.pagenav a.extwin span.inner {
background-position: -29px -271px;
}
a.button.changeformat.html span.icon,
.pagenav a.changeformat.html span.inner {
background-position: -7px -1859px;
}
a.button.changeformat.html.selected span.icon,
.pagenav a.changeformat.html.selected span.inner {
background-position: -29px -1859px;
}
a.button.changeformat.text span.icon,
.pagenav a.changeformat.text span.inner {
background-position: -7px -1874px;
}
a.button.changeformat.text.selected span.icon,
.pagenav a.changeformat.text.selected span.inner {
background-position: -29px -1874px;
}
a.button.add span.icon {
background-position: -7px -2009px;
}
a.button.delete span.icon {
background-position: -29px -2009px;
}
.pagenav .countdisplay {
display: inline-block;
padding: 3px 1em 0 1em;
text-shadow: 0px 1px 1px #fff;
min-width: 16em;
}
.pagenavbuttons {
position: relative;
top: -2px;
}
a.iconbutton {
display: inline-block;
width: 20px;
height: 18px;
text-decoration: none;
text-indent: -5000px;
background: url(images/buttons.png) -1000px 0 no-repeat;
}
a.iconbutton.disabled {
opacity: 0.4;
filter: alpha(opacity=40);
cursor: default;
}
a.iconbutton.searchoptions {
width: 24px;
background-position: -2px -317px;
}
a.iconbutton.reset {
width: 24px;
background-position: -25px -317px;
}
a.iconbutton.remove,
a.iconbutton.cancel {
background-position: -7px -378px;
}
a.iconbutton.delete {
background-position: -7px -338px;
}
a.iconbutton.add {
background-position: -7px -358px;
}
a.iconbutton.remove {
background-position: -7px -379px;
}
a.iconbutton.cancel {
background-position: -7px -398px;
}
a.iconbutton.edit {
background-position: -7px -418px;
}
a.iconbutton.upload {
background-position: -6px -438px;
}
a.iconlink {
display: inline-block;
color: #888;
text-decoration: none;
white-space: nowrap;
padding: 2px 8px 2px 20px;
background: url(images/buttons.png) -1000px 0 no-repeat;
}
a.iconlink:hover {
text-decoration: underline;
}
a.iconlink.delete {
background-position: -7px -337px;
}
a.iconlink.add {
background-position: -7px -357px;
}
a.iconlink.remove {
background-position: -7px -378px;
}
a.iconlink.cancel {
background-position: -7px -397px;
}
a.iconlink.edit {
background-position: -7px -417px;
}
a.iconlink.upload {
background-position: -6px -437px;
}
/*** message bar ***/
#message div.loading,
#message div.warning,
#message div.error,
#message div.notice,
#message div.confirmation,
#message-objects div.notice {
color: #555;
font-weight: bold;
padding: 6px 30px 6px 25px;
display: inline-block;
white-space: nowrap;
background: url(images/messages.png) 0 5px no-repeat;
cursor: default;
}
#message div.warning {
color: #960;
background-position: 0 -86px;
}
#message div.error {
color: #cf2734;
background-position: 0 -55px;
}
#message div.confirmation {
color: #093;
background-position: 0 -25px;
}
#message div.loading {
background: url(images/ajaxloader.gif) 2px 6px no-repeat;
}
#message div a,
#message div span {
padding-right: 0.5em;
text-decoration: none;
}
#message div a:hover {
text-decoration: underline;
cursor: pointer;
}
#message.statusbar {
display: none;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 27px;
padding-left: 8px;
border-top: 1px solid #ddd;
border-radius: 0 0 4px 4px;
background: #eaeaea;
background: -moz-linear-gradient(top, #eaeaea 0%, #c8c8c8 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#eaeaea), color-stop(100%,#c8c8c8));
background: -o-linear-gradient(top, #eaeaea 0%, #c8c8c8 100%);
background: -ms-linear-gradient(top, #eaeaea 0%, #c8c8c8 100%);
background: linear-gradient(top, #eaeaea 0%, #c8c8c8 100%);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#messagestack {
position: absolute;
bottom: 20px;
right: 12px;
z-index: 50000;
width: auto;
height: auto;
max-height: 85%;
overflow-y: auto;
padding: 2px;
}
#messagestack div {
display: block;
position: relative;
width: 280px;
height: auto;
min-height: 16px;
margin: 3px 2px 5px 2px;
padding: 8px 10px 7px 30px;
cursor: default;
font-size: 12px;
font-weight: bold;
border-radius: 4px;
border: 1px solid #444;
color: #ebebeb;
text-shadow: 0 1px 1px #000;
background: rgba(64,64,64,0.85);
background: -moz-linear-gradient(top, rgba(64,64,64,0.85) 0%, rgba(48,48,48,0.9) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(64,64,64,0.85)), color-stop(100%,rgba(48,48,48,0.9)));
background: -webkit-linear-gradient(top, rgba(64,64,64,0.85) 0%, rgba(48,48,48,0.85) 100%);
background: -o-linear-gradient(top, rgba(64,64,64,0.85) 0%, rgba(48,48,48,0.85) 100%);
background: -ms-linear-gradient(top, rgba(64,64,64,0.85) 0%, rgba(48,48,48,0.85) 100%);
background: linear-gradient(to bottom, rgba(64,64,64,0.85) 0%, rgba(48,48,48,0.85) 100%);
-moz-box-shadow: 0 1px 4px 0 rgba(50,50,50,0.8), inset 0px 1px 0 0px #888;
-webkit-box-shadow: 0 1px 4px 0 rgba(50,50,50,0.8), inset 0px 1px 0 0px #888;
-o-box-shadow: 0 1px 4px 0 rgba(50,50,50,0.8), inset 0px 1px 0 0px #888;
box-shadow: 0 1px 4px 0 rgba(50,50,50,0.8), inset 0px 1px 0 0px #888;
}
#messagestack div:after {
content: "";
position: absolute;
display: block;
top: 0;
left: 4px;
width: 20px;
height: 24px;
background: url(images/messages_dark.png) 0 7px no-repeat;
}
#messagestack div.error {
color: #ff615d;
}
#messagestack div.error:after {
background-position: 0 -55px;
}
#messagestack div.warning {
color: #f4bf0e;
}
#messagestack div.warning:after {
background-position: 0 -84px;
}
#messagestack div.confirmation {
color: #00e05a;
}
#messagestack div.confirmation:after {
background-position: 0 -25px;
}
#messagestack div.loading {
color: #ddd;
}
#messagestack div.loading:after {
top: 4px;
left: 6px;
background: url(images/ajaxloader_dark.gif) 0 4px no-repeat;
}
#messagestack div a {
color: #94c0da;
}
#messagestack div a:hover {
text-decoration: underline;
cursor: pointer;
}
.ui-dialog.error .ui-dialog-title,
.ui-dialog.warning .ui-dialog-title,
.ui-dialog.confirmation .ui-dialog-title {
padding-left: 25px;
background: url(images/messages.png) 0 5px no-repeat;
text-shadow: 0 1px 1px #fff;
}
.ui-dialog.warning .ui-dialog-title {
color: #960;
background-position: 0 -90px;
}
.ui-dialog.error .ui-dialog-title {
color: #cf2734;
background-position: 0 -60px;
}
.ui-dialog.confirmation .ui-dialog-title {
color: #093;
background-position: 0 -30px;
}
.ui-dialog.popupmessage .ui-dialog-titlebar {
padding: 8px 1em 4px 1em;
background: #e3e3e3;
background: -moz-linear-gradient(top, #e3e3e3 0%, #cfcfcf 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e3e3e3), color-stop(100%,#cfcfcf));
background: -o-linear-gradient(top, #e3e3e3 0%, #cfcfcf 100%);
background: -ms-linear-gradient(top, #e3e3e3 0%, #cfcfcf 100%);
background: linear-gradient(top, #e3e3e3 0%, #cfcfcf 100%);
}
.ui-dialog.popupmessage .ui-widget-content {
font-size: 12px;
background: #eee;
background: -moz-linear-gradient(top, #eee 0%, #dcdcdc 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#eee), color-stop(100%,#dcdcdc));
background: -o-linear-gradient(top, #eee 0%, #dcdcdc 100%);
background: -ms-linear-gradient(top, #eee 0%, #dcdcdc 100%);
background: linear-gradient(top, #eee 0%, #dcdcdc 100%);
}
/*** basic page layout ***/
#header {
overflow-x: hidden; /* Chrome bug #1488851 */
}
#topline {
height: 18px;
background: url(images/linen_header.jpg) repeat #666;
border-bottom: 1px solid #4f4f4f;
padding: 2px 0 2px 10px;
color: #aaa;
text-align: center;
}
#topnav {
position: relative;
height: 46px;
margin-bottom: 10px;
padding: 0 0 0 10px;
background: #111;
background: -moz-linear-gradient(top, #404040 0%, #060606 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#404040), color-stop(100%,#060606));
background: -o-linear-gradient(top, #404040 0%, #060606 100%);
background: -ms-linear-gradient(top, #404040 0%, #060606 100%);
background: linear-gradient(top, #404040 0%, #060606 100%);
}
#topline a,
#topnav a {
color: #eee;
text-decoration: none;
}
#topline a:hover {
text-decoration: underline;
}
#toplogo {
padding-top: 2px;
cursor: pointer;
border: none;
}
.topleft {
float: left;
}
.topright {
float: right;
}
.closelink {
display: inline-block;
padding: 2px 10px 2px 20px;
}
#topline span.username {
padding-right: 1em;
}
#topline .topleft a {
display: inline-block;
padding: 2px 0.8em 0 0;
color: #aaa;
}
#topline a.button-logout {
display: inline-block;
padding: 2px 10px 2px 20px;
background: url(images/buttons.png) -6px -193px no-repeat;
color: #fff;
}
#taskbar .button-logout {
display: none;
}
#taskbar a.button-logout span.button-inner {
background-position: -2px -1791px;
}
#taskbar a.button-logout:hover span.button-inner {
background-position: -2px -1829px;
}
/*** minimal version of the page header ***/
.minimal #topline {
position: fixed;
top: -18px;
background: #444;
z-index: 5000;
width: 100%;
height: 22px;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.minimal #topline:hover {
top: 0px;
opacity: 0.94;
filter: alpha(opacity=94);
-webkit-transition: top 0.3s ease-in-out;
-moz-transition: top 0.3s ease-in-out;
-o-transition: top 0.3s ease-in-out;
transition: top 0.3s ease-in-out;
}
.extwin #topline,
.extwin #topline:hover {
position: static;
top: 0px;
height: 18px;
width: auto;
-moz-box-sizing: content-box;
box-sizing: content-box;
opacity: 0.999;
}
.minimal #topline a.button-logout {
display: none;
}
.minimal #topline span.username {
display: inline-block;
padding-top: 2px;
}
.minimal #topnav {
position: relative;
top: 4px;
height: 42px;
}
.minimal #taskbar a {
position: relative;
padding: 10px 10px 0 6px;
height: 32px;
}
.minimal #taskbar .button-logout {
display: inline-block;
}
.minimal #taskbar .button-inner {
top: -4px;
padding: 0;
height: 24px !important;
width: 27px;
text-indent: -5000px;
}
#taskbar .tooltip {
display: none;
}
.minimal #taskbar .tooltip {
position: absolute;
top: -500px;
right: 2px;
display: inline-block;
padding: 2px 8px 3px 8px;
background: #444;
background: -moz-linear-gradient(top, #444 0%, #333 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#444), color-stop(100%,#333));
background: -o-linear-gradient(top, #444 0%, #333 100%);
background: -ms-linear-gradient(top, #444 0%, #333 100%);
background: linear-gradient(top, #444 0%, #333 100%);
color: #eee;
font-weight: bold;
white-space: nowrap;
border: 1px solid #777;
box-shadow: 0 1px 5px 0 #333;
-moz-box-shadow: 0 1px 5px 0 #333;
-webkit-box-shadow: 0 1px 5px 0 #333;
-o-box-shadow: 0 1px 5px 0 #333;
z-index: 200;
white-space: nowrap;
text-shadow: 0px 1px 1px #000;
}
.minimal #taskbar .tooltip:after {
content: "";
position: absolute;
top: -4px;
right: 15px;
border-style: solid;
border-width: 0 4px 4px;
border-color: #888 transparent;
/* reduce the damage in FF3.0 */
display: block;
width: 0;
z-index: 251;
}
.minimal #taskbar a:hover .tooltip {
display: block;
top: 39px;
}
/*** taskbar ***/
#taskbar {
position: relative;
padding-right: 18px;
}
#taskbar a {
display: inline-block;
height: 34px;
padding: 12px 10px 0 6px;
}
#taskbar a span.button-inner {
display: inline-block;
font-size: 110%;
font-weight: normal;
text-shadow: 0px 1px 1px black;
padding: 5px 0 0 34px;
height: 19px;
background: url(images/buttons.png) -1000px 0 no-repeat;
}
#taskbar a.button-selected {
color: #3cf;
background-color: #2c2c2c;
}
#taskbar a.button-mail span.button-inner {
background-position: 0 2px;
}
#taskbar a.button-mail:hover span.button-inner,
#taskbar a.button-mail.button-selected span.button-inner {
background-position: 0 -22px;
}
#taskbar a.button-addressbook span.button-inner {
background-position: 0 -48px;
}
#taskbar a.button-addressbook:hover span.button-inner,
#taskbar a.button-addressbook.button-selected span.button-inner {
background-position: 0 -72px;
}
#taskbar a.button-settings span.button-inner {
background-position: 0 -96px;
}
#taskbar a.button-settings:hover span.button-inner,
#taskbar a.button-settings.button-selected span.button-inner {
background-position: 0 -120px;
}
#taskbar a.button-calendar span.button-inner {
background-position: 0 -144px;
}
#taskbar a.button-calendar:hover span.button-inner,
#taskbar a.button-calendar.button-selected span.button-inner {
background-position: 0 -168px;
}
#taskbar .minmodetoggle {
position: absolute;
top: 0;
right: 0;
display: block;
width: 19px;
height: 46px;
cursor: pointer;
background: url(images/buttons.png) -35px -1778px no-repeat;
}
.minimal #taskbar .minmodetoggle {
height: 42px;
background-position: -35px -1820px;
}
#mainscreen {
position: absolute;
top: 88px;
left: 10px;
right: 10px;
bottom: 20px;
}
#mainscreencontent {
position: absolute;
top: 42px;
left: 0;
right: 0;
bottom: 0;
}
.minimal #mainscreen {
top: 62px;
}
.minimal #mainscreen.offset {
top: 102px;
}
.extwin #mainscreen {
top: 40px;
}
#mainscreen.offset {
top: 132px;
}
#mainscreen .offset {
top: 42px;
}
.uibox {
border: 1px solid #a3a3a3;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 0 2px #999;
-o-box-shadow: 0 0 2px #999;
-webkit-box-shadow: 0 0 2px #999;
-moz-box-shadow: 0 0 2px #999;
background: #fff;
}
.minwidth {
min-width: 1024px;
}
.scroller {
overflow: auto;
}
.readtext {
width: 42em;
padding: 12px;
font-size: 12px;
}
.readtext > h1,
.readtext > h2,
.readtext > h3 {
margin-top: 0;
}
.watermark {
background-image: url(images/watermark.jpg);
background-position: center;
background-repeat: no-repeat;
}
/* fix scrolling within iframes in webkit browsers on touch devices */
@media screen and (-webkit-min-device-pixel-ratio:0) and (max-device-width:1024px) {
.iframebox {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
}
/*** lists ***/
.listbox {
background: #d9ecf4;
overflow: hidden;
}
.listbox .scroller {
position: absolute;
top: 0;
left: 0;
width: 100%;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
}
.listbox .scroller.withfooter {
bottom: 42px;
}
.listbox .boxtitle + .scroller {
top: 34px;
}
.boxtitle,
.uibox .listing thead td {
font-size: 12px;
font-weight: bold;
padding: 7px 8px 6px 8px;
line-height: 20px;
margin: 0;
text-shadow: 0px 1px 1px #fff;
border-bottom: 1px solid #bbd3da;
white-space: nowrap;
}
.uibox .boxtitle,
.uibox .listing thead td {
background: #b0ccd7;
color: #004458;
border-radius: 4px 4px 0 0;
}
.listbox .listitem,
.listbox .tablink,
.listing tbody td,
.listing li {
display: block;
border-top: 1px solid #fff;
border-bottom: 1px solid #bbd3da;
cursor: default;
font-weight: normal;
}
.listbox .listitem a,
.listbox .tablink a,
.listing tbody td,
.listing li a {
display: block;
color: #376572;
text-shadow: 0px 1px 1px #fff;
text-decoration: none;
cursor: default;
padding: 4px 8px;
line-height: 17px;
white-space: nowrap;
}
.listing tbody td {
display: table-cell;
height: auto;
min-height: 14px;
}
.webkit .listing tbody td {
height: 14px;
}
.listbox .listitem.selected,
.listbox .tablink.selected,
.listbox .listitem.selected > a,
.listbox .tablink.selected > a,
.listing tbody tr.unfocused td,
.listing tbody tr.selected td,
.listing li.selected,
.listing li.selected > a {
color: #004458;
font-weight: bold;
background-color: #c7e3ef;
}
ul.listing {
display: block;
list-style: none;
margin: 0;
padding: 0;
}
ul.listing li {
background-color: #d9ecf4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
ul.listing li ul {
border-top: 1px solid #bbd3da;
}
ul.listing li.droptarget,
table.listing tr.droptarget td {
background-color: #e8e798;
}
.listbox table.listing {
background-color: #d9ecf4;
}
table.listing,
table.layout {
border: 0;
width: 100%;
border-spacing: 0;
}
table.layout td {
vertical-align: top;
}
ul.treelist li {
position: relative;
}
ul.treelist li ul {
margin: 0;
padding: 0;
}
ul.treelist li ul li:last-child {
border-bottom: 0;
}
ul.treelist li a {
display: block;
padding-left: 20px;
overflow: hidden;
text-overflow: ellipsis;
}
ul.treelist ul li a {
padding-left: 38px;
}
ul.treelist ul ul li a {
padding-left: 54px;
}
ul.treelist.iconized li a {
padding-left: 36px;
}
ul.treelist.iconized li ul li a {
padding-left: 62px;
}
ul.treelist li div.treetoggle {
position: absolute;
top: 7px;
left: 4px;
width: 13px;
height: 13px;
background: url(images/listicons.png) -3px -144px no-repeat;
cursor: pointer;
}
ul.treelist li ul li div.treetoggle {
left: 22px;
}
ul.treelist.iconized li div.treetoggle {
top: 13px;
left: 19px;
}
ul.treelist.iconized li ul li div.treetoggle {
left: 35px;
}
ul.treelist li div.treetoggle.expanded {
background-position: -3px -168px;
}
ul.treelist li.selected > div.collapsed {
background-position: -23px -144px;
}
ul.treelist li.selected > div.expanded {
background-position: -23px -168px;
}
.listbox .boxfooter {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 42px;
border-top: 1px solid #ccdde4;
background: #d9ecf4;
-webkit-box-shadow: inset 0 1px 0 0 #fff;
-moz-box-shadow: inset 0 1px 0 0 #fff;
box-shadow: inset 0 1px 0 0 #fff;
white-space: nowrap;
overflow: hidden;
}
.uibox .boxfooter {
border-radius: 0 0 4px 4px;
}
.boxfooter .listbutton {
display: inline-block;
text-decoration: none;
width: 48px;
border-right: 1px solid #fff;
background: #c7e3ef;
padding: 3px 0;
margin-top: 1px;
}
.uibox .boxfooter .listbutton:first-child {
border-radius: 0 0 0 4px;
}
.boxfooter .listbutton .inner {
display: inline-block;
width: 48px;
height: 35px;
text-indent: -5000px;
background: url(images/buttons.png) -1000px 0 no-repeat;
}
.boxfooter .listbutton.add .inner {
background-position: 10px -1301px;
}
.boxfooter .listbutton.delete .inner {
background-position: 10px -1342px;
}
.boxfooter .listbutton.groupactions .inner {
background-position: 5px -1382px;
}
.boxfooter .listbutton.addto .inner {
background-position: 5px -1422px;
}
.boxfooter .listbutton.addcc .inner {
background-position: 5px -1462px;
}
.boxfooter .listbutton.addbcc {
width: 54px;
}
.boxfooter .listbutton.addbcc .inner {
width: 54px;
background-position: 2px -1502px;
}
.boxfooter .listbutton.removegroup .inner {
background-position: 5px -1540px;
}
.boxfooter .listbutton.disabled .inner {
opacity: 0.4;
filter: alpha(opacity=40);
}
.boxfooter .countdisplay {
display: inline-block;
position: relative;
top: 10px;
color: #69929e;
padding: 3px 6px;
}
.boxpagenav {
position: absolute;
top: 10px;
right: 6px;
width: auto;
}
.boxpagenav a.icon {
display: inline-block;
padding: 1px 3px;
height: 13px;
width: 14px;
text-indent: 1000px;
vertical-align: bottom;
overflow: hidden;
background: url(images/buttons.png) -4px -286px no-repeat;
}
.boxpagenav a.icon.prevpage {
background-position: -4px -301px;
}
.boxpagenav a.icon.nextpage {
background-position: -28px -301px;
}
.boxpagenav a.icon.lastpage {
background-position: -28px -286px;
}
.boxpagenav a.icon.disabled {
opacity: 0.4;
filter: alpha(opacity=40);
}
.centerbox {
width: 40em;
margin: 16px auto;
}
.errorbox {
width: 40em;
padding: 20px;
}
.errorbox h3 {
font-size: 16px;
margin-top: 0;
}
/*** Records table ***/
table.records-table {
display: table;
width: 100%;
table-layout: fixed;
border-spacing: 0;
border: 1px solid #bbd3da;
}
.boxlistcontent .records-table {
border: 0;
}
.records-table thead td {
color: #69939e;
font-size: 11px;
font-weight: bold;
background: #d6eaf3;
background: -moz-linear-gradient(left, #e3f2f6 0, #d6eaf3 14px, #d6eaf3 100%);
background: -webkit-gradient(linear, left top, right top, color-stop(0,#e3f2f6), color-stop(8%,#d6eaf3), color-stop(100%,#d6eaf3));
background: -o-linear-gradient(left, #e3f2f6 0, #d6eaf3 14px, #d6eaf3 100%);
background: -ms-linear-gradient(left, #e3f2f6 0, #d6eaf3 14px ,#d6eaf3 100%);
background: linear-gradient(left, #e3f2f6 0, #d6eaf3 14px, #d6eaf3 100%);
border-left: 1px solid #bbd3da;
padding: 8px 7px;
overflow: hidden;
text-overflow: ellipsis;
}
.records-table.sortheader thead td {
padding: 0;
}
.records-table thead td a,
.records-table thead td span {
display: block;
padding: 7px 7px;
color: #69939e;
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
}
.records-table tbody td {
padding: 2px 7px;
border-bottom: 1px solid #ddd;
border-left: 1px dotted #bbd3da;
white-space: nowrap;
cursor: default;
overflow: hidden;
text-overflow: ellipsis;
background-color: #fff;
}
/* This padding-left minus the focused padding left should be half of the focused border-left */
.records-table thead tr td:first-child,
.records-table tbody tr td:first-child {
border-left: 0;
padding-left: 6px;
}
/* because of border-collapse, we make the left border twice what we want it to be - half will be hidden to the left */
.records-table tbody tr.focused > td:first-child {
border-left: 2px solid #b0ccd7;
padding-left: 4px;
}
.records-table tbody tr.selected.focused > td:first-child {
border-left-color: #49b3d2;
}
.records-table tr.selected td {
color: #fff !important;
background: #019bc6;
background: -moz-linear-gradient(top, #019bc6 0%, #017cb4 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#019bc6), color-stop(100%,#017cb4));
background: -o-linear-gradient(top, #019bc6 0%, #017cb4 100%);
background: -ms-linear-gradient(top, #019bc6 0%, #017cb4 100%);
background: linear-gradient(top, #019bc6 0%, #017cb4 100%);
}
.records-table tr.selected td a,
.records-table tr.selected td span {
color: #fff !important;
}
.records-table tr.unfocused td {
color: #fff !important;
background-color: #4db0d2 !important;
}
.records-table tr.unfocused td a,
.records-table tr.unfocused td span {
color: #fff !important;
}
.records-table tr.deleted td,
.records-table tr.deleted td a {
color: #ccc !important;
}
/*** iFrames ***/
#aboutframe {
width: 97%;
height: 100%;
border: 0;
padding: 0;
}
body.iframe {
background: #fff;
margin: 38px 0 10px 0;
}
body.iframe.error {
background: #ededed;
}
body.iframe.floatingbuttons {
margin-bottom: 40px;
}
body.iframe.fullheight {
margin: 0;
}
.contentbox .boxtitle,
body.iframe .boxtitle {
color: #777;
background: #eee;
background: -moz-linear-gradient(top, #eee 0%, #dfdfdf 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#eee), color-stop(100%,#dfdfdf));
background: -o-linear-gradient(top, #eee 0%, #dfdfdf 100%);
background: -ms-linear-gradient(top, #eee 0%, #dfdfdf 100%);
background: linear-gradient(top, #eee 0%, #dfdfdf 100%);
border-bottom: 1px solid #ccc;
}
body.iframe .boxtitle {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 100;
}
body.iframe .footerleft.floating,
#composeview-bottom .formbuttons.floating {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
z-index: 110;
background: #fff;
padding-top: 8px;
padding-bottom: 12px;
}
body.iframe .footerleft.floating:before,
#composeview-bottom .formbuttons.floating:before {
content: " ";
position: absolute;
top: -6px;
left: 0;
width: 100%;
height: 6px;
background: url(images/overflowshadow.png) top center no-repeat;
}
.boxcontent {
padding: 10px;
}
.contentbox .scroller {
position: absolute;
top: 34px;
left: 0;
right: 0;
bottom: 0px;
overflow: auto;
}
.iframebox {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0px;
}
.footerleft {
padding: 0 12px 4px 12px;
}
.propform fieldset {
margin-bottom: 20px;
border: 0;
padding: 0;
}
.propform fieldset legend {
display: block;
font-size: 14px;
font-weight: bold;
padding-bottom: 10px;
margin-bottom: 0;
}
.propform fieldset fieldset legend {
color: #666;
font-size: 12px;
}
.propform div.prop {
margin-bottom: 0.5em;
}
.propform div.prop.block label {
display: block;
margin-bottom: 0.3em;
}
.propform div.prop.block input,
.propform div.prop.block textarea {
width: 95%;
}
.propform a.disabled {
color: #999;
text-decoration: none;
cursor: default;
}
fieldset.floating {
float: left;
margin-right: 10px;
margin-bottom: 10px;
}
table.propform {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
}
ul.proplist li,
table.propform td {
width: 80%;
padding: 4px 10px;
background: #eee;
border-bottom: 2px solid #fff;
}
table.propform td.title {
width: 20%;
color: #333;
padding-right: 20px;
white-space: nowrap;
}
table.propform .mceLayout td {
padding: 0;
border-bottom: 0;
}
ul.proplist {
list-style: none;
margin: 0;
padding: 0;
}
ul.proplist li {
width: auto;
}
#pluginbody {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/*** Login form ***/
#login-form {
position: relative;
width: 580px;
margin: 20ex auto 2ex auto;
}
#login-form .box-inner {
width: 430px;
background: url(images/linen_login.jpg) top left no-repeat #5c5c5c;
margin: 0 50px;
padding: 10px 24px 24px 24px;
border: 1px solid #333;
border-radius: 5px;
box-shadow: inset 0 0 1px #ccc;
-o-box-shadow: inset 0 0 1px #ccc;
-webkit-box-shadow: inset 0 0 1px #ccc;
-moz-box-shadow: inset 0 0 1px #ccc;
}
#login-form .box-bottom {
background: url(images/login_shadow.png) top center no-repeat;
margin-top: -3px;
padding-top: 10px;
}
#login-form .noscriptwarning {
margin: 0 auto;
width: 430px;
color: #cf2734;
font-size: 110%;
font-weight: bold;
}
#login-form td.input {
width: 80%;
padding: 8px;
}
#login-form input[type="text"],
#login-form input[type="password"] {
width: 100%;
border-color: #666;
}
#login-form input.button {
color: #444;
text-shadow: 0px 1px 1px #fff;
border-color: #f9f9f9;
background: #f9f9f9;
background: -moz-linear-gradient(top, #f9f9f9 0%, #e2e2e2 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f9f9f9), color-stop(100%,#e2e2e2));
background: -o-linear-gradient(top, #f9f9f9 0%, #e2e2e2 100%);
background: -ms-linear-gradient(top, #f9f9f9 0%, #e2e2e2 100%);
background: linear-gradient(top, #f9f9f9 0%, #e2e2e2 100%);
box-shadow: inset 0 1px 0 0 #fff;
-moz-box-shadow: inset 0 1px 0 0 #fff;
-webkit-box-shadow: inset 0 1px 0 0 #fff;
-o-box-shadow: inset 0 1px 0 0 #fff;
}
#login-form input.button:hover,
#login-form input.button:focus {
box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9), inset 0 1px 0 0 #fff;
-moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9), inset 0 1px 0 0 #fff;
-webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9), inset 0 1px 0 0 #fff;
-o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9), inset 0 1px 0 0 #fff;
}
#login-form input.button:active {
color: #333;
background: -moz-linear-gradient(top, #dcdcdc 0%, #f9f9f9 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#dcdcdc), color-stop(100%,#f9f9f9));
background: -o-linear-gradient(top, #dcdcdc 0%, #f9f9f9 100%);
background: -ms-linear-gradient(top, #dcdcdc 0%, #f9f9f9 100%);
background: linear-gradient(top, #dcdcdc 0%, #f9f9f9 100%);
}
#login-form form table {
width: 98%;
}
#login-form td.title {
width: 20%;
white-space: nowrap;
color: #cecece;
text-shadow: 0px 1px 1px black;
text-align: right;
padding-right: 1em;
}
#login-form p.formbuttons {
margin-top: 2em;
text-align: center;
}
#login-form #logo {
margin-bottom: 20px;
border: none;
}
#login-form #message {
min-height: 40px;
padding: 5px 25px;
text-align: center;
font-size: 1.1em;
}
#login-form #message div {
display: inline-block;
padding-right: 0;
font-size: 12px;
}
#bottomline {
font-size: 90%;
text-align: center;
margin-top: 2em;
}
/*** quicksearch **/
.searchbox {
position: relative;
}
#quicksearchbar {
position: absolute;
right: 2px;
top: 2px;
width: 240px;
}
.searchbox input,
#quicksearchbar input {
width: 176px;
margin: 0;
padding: 3px 30px 3px 34px;
height: 18px;
background: #f1f1f1;
border-color: #ababab;
font-weight: bold;
font-size: 11px;
}
.searchbox #searchmenulink,
#quicksearchbar #searchmenulink {
position: absolute;
top: 5px;
left: 6px;
}
.searchbox #searchreset,
#quicksearchbar #searchreset {
position: absolute;
top: 4px;
right: 1px;
}
/*** toolbar ***/
.toolbar .spacer {
display: inline-block;
width: 24px;
height: 40px;
padding: 0;
}
.toolbar a.button {
text-align: center;
font-size: 10px;
color: #555;
min-width: 50px;
max-width: 75px;
height: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 28px 2px 0 2px;
text-shadow: 0px 1px 1px #eee;
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
-o-box-shadow: none;
background: url(images/buttons.png) -100px 0 no-repeat transparent;
border: 0;
border-radius: 0;
}
.toolbar a.button.disabled {
opacity: 0.4;
filter: alpha(opacity=40);
}
.dropbutton {
display: inline-block;
position: relative;
}
.dropbutton .dropbuttontip {
display: block;
position: absolute;
right: 0;
top: 0;
height: 42px;
width: 18px;
background: url(images/buttons.png) 0 -1255px no-repeat;
cursor: pointer;
}
.dropbutton .dropbuttontip:hover {
background-position: -26px -1255px;
}
.dropbutton a.button.disabled + .dropbuttontip {
opacity: 0.5;
filter: alpha(opacity=50);
}
.dropbutton a.button.disabled + .dropbuttontip:hover {
background-position: 0 -1255px;
}
.dropbutton a.button {
margin-left: 0;
padding-left: 0;
margin-right: 0;
padding-right: 0;
}
.toolbar a.button.back {
background-position: 0 -1216px;
}
.toolbar a.button.checkmail {
background-position: center -1176px;
}
.toolbar a.button.compose {
background-position: center -530px;
}
.toolbar a.button.reply {
background-position: center -570px;
}
.toolbar a.button.reply-all {
min-width: 64px;
background-position: left -610px;
}
.toolbar a.button.forward {
min-width: 64px;
background-position: left -650px;
}
.toolbar a.button.delete {
background-position: center -690px;
}
.toolbar a.button.archive {
background-position: center -730px;
}
.toolbar a.button.junk {
background-position: center -770px;
}
.toolbar a.button.print {
background-position: center -810px;
}
.toolbar a.button.markmessage {
background-position: center -1094px;
}
.toolbar a.button.move {
background-position: center -1971px;
}
.toolbar a.button.more {
background-position: center -850px;
}
.toolbar a.button.attach {
background-position: center -890px;
}
.toolbar a.button.spellcheck {
min-width: 64px;
background-position: left -930px;
}
.toolbar a.button.spellcheck.selected {
background-position: left -1620px;
color: #1978a1;
}
.toolbar a.button.insertsig {
background-position: center -1135px;
}
.toolbar a.button.search {
background-position: center -970px;
}
.toolbar a.button.import {
background-position: center -1012px;
}
.toolbar a.button.export {
min-width: 74px;
background-position: center -1054px;
}
.toolbar a.button.send {
background-position: center -1660px;
}
.toolbar a.button.savedraft {
background-position: center -1700px;
}
.toolbar a.button.close {
background-position: 0 -1745px;
}
.toolbar a.button.download {
background-position: center -1892px;
}
.toolbar a.button.responses {
background-position: center -1932px;
}
a.menuselector {
display: inline-block;
border: 1px solid #ababab;
border-radius: 4px;
background: #f8f8f8;
background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));
background: -o-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);
background: -ms-linear-gradient(top, #f9f9f9 0%, #dddddd 100%);
background: linear-gradient(top, #f8f8f8 0%, #dddddd 100%);
text-decoration: none;
color: #333;
cursor: pointer;
white-space: nowrap;
}
a.menuselector .handle {
display: inline-block;
padding: 0 32px 0 6px;
height: 20px;
line-height: 19px;
text-shadow: 0px 1px 1px #fff;
background: url(images/selector.png) right center no-repeat;
border-radius: 4px;
}
a.menuselector:active {
background: #dddddd;
background: -moz-linear-gradient(top, #dddddd 0%, #f8f8f8 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#dddddd), color-stop(100%,#f8f8f8));
background: -o-linear-gradient(top, #dddddd 0%, #f8f8f8 100%);
background: -ms-linear-gradient(top, #dddddd 0%, #f8f8f8 100%);
background: linear-gradient(top, #dddddd 0%, #f8f8f8 100%);
text-decoration: none;
}
select.decorated {
position: relative;
z-index: 10;
opacity: 0;
height: 22px;
cursor: pointer;
filter: alpha(opacity=0);
-khtml-appearance: none;
-webkit-appearance: none;
border: 0;
}
html.opera select.decorated {
opacity: 1;
}
select.decorated option {
color: #fff;
background: #444;
border: 0;
border-top: 1px solid #5a5a5a;
border-bottom: 1px solid #333;
text-shadow: 0px 1px 1px #333;
padding: 4px 6px;
outline: none;
cursor: default;
}
/*** quota indicator ***/
#quotadisplay {
left: 6px;
height: 18px;
font-size: 12px;
font-weight: bold;
text-shadow: 0px 1px 1px #fff;
padding-left: 30px;
background: url(images/quota.png) -100px 0 no-repeat;
}
/*** popup menus ***/
.popupmenu,
#rcmKSearchpane {
display: none;
position: absolute;
top: 32px;
left: 90px;
width: auto;
max-height: 70%;
overflow: -moz-scrollbars-vertical;
overflow-y: auto;
background: #444;
border: 1px solid #999;
z-index: 240;
border-radius: 4px;
box-shadow: 0 2px 6px 0 #333;
-moz-box-shadow: 0 2px 6px 0 #333;
-webkit-box-shadow: 0 2px 6px 0 #333;
-o-box-shadow: 0 2px 6px 0 #333;
}
.popupmenu.dropdown {
border-radius: 0 0 4px 4px;
border-top: 0;
}
ul.toolbarmenu,
ul.toolbarmenu ul,
#rcmKSearchpane ul {
margin: 0;
padding: 0;
list-style: none;
}
.googie_list td,
ul.toolbarmenu li,
#rcmKSearchpane ul li {
color: #fff;
white-space: nowrap;
min-width: 130px;
margin: 0;
border-top: 1px solid #5a5a5a;
border-bottom: 1px solid #333;
}
.googie_list tr:first-child td,
ul.toolbarmenu > li:first-child,
select.decorated option:first-child {
border-top: 0;
}
.googie_list tr:last-child td,
ul.toolbarmenu > li:last-child,
select.decorated option:last-child {
border-bottom: 0;
}
.googie_list td span,
ul.toolbarmenu li a {
display: block;
color: #666;
text-shadow: 0px 1px 1px #333;
text-decoration: none;
min-height: 14px;
padding: 6px 16px 6px 10px;
}
.googie_list td span {
padding: 3px 10px;
}
.googie_list td span,
ul.toolbarmenu li a.active {
color: #fff;
cursor: default;
}
.googie_list td.googie_list_onhover,
ul.toolbarmenu li a.active:hover,
#rcmKSearchpane ul li.selected,
select.decorated option:hover,
select.decorated option[selected='selected'] {
background-color: #00aad6;
background: -moz-linear-gradient(top, #00aad6 0%, #008fc9 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#00aad6), color-stop(100%,#008fc9));
background: -o-linear-gradient(top, #00aad6 0%, #008fc9 100%);
background: -ms-linear-gradient(top, #00aad6 0%, #008fc9 100%);
background: linear-gradient(top, #00aad6 0%, #008fc9 100%);
}
ul.toolbarmenu.iconized li a,
ul.toolbarmenu.selectable li a {
padding-left: 30px;
}
ul.toolbarmenu.selectable li a.selected {
background: url(images/messages.png) 4px -27px no-repeat;
}
ul.toolbarmenu li label {
display: block;
color: #fff;
padding: 4px 8px;
text-shadow: 0px 1px 1px #333;
}
ul.toolbarmenu li.separator label {
color: #bbb;
font-style: italic;
}
ul.toolbarmenu li a.icon {
color: #eee;
padding: 2px 6px;
}
ul.toolbarmenu li span.icon {
display: block;
min-height: 14px;
padding: 4px 4px 1px 24px;
height: 17px;
background-image: url(images/listicons.png);
background-position: -100px 0;
background-repeat: no-repeat;
opacity: 0.2;
filter: alpha(opacity=20);
}
ul.toolbarmenu li a.active span.icon {
opacity: 0.99;
filter: alpha(opacity=100);
}
ul.toolbarmenu li span.read {
background-position: 0 -1220px;
}
ul.toolbarmenu li span.unread {
background-position: 0 -1196px;
}
ul.toolbarmenu li span.flagged {
background-position: 0 -1244px;
}
ul.toolbarmenu li span.unflagged {
background-position: 0 -1268px;
}
ul.toolbarmenu li span.mail {
background-position: 0 -1293px;
}
ul.toolbarmenu li span.list {
background-position: 0 -1317px;
}
ul.toolbarmenu li span.invert {
background-position: 0 -1340px;
}
ul.toolbarmenu li span.cross {
background-position: 0 -1365px;
}
ul.toolbarmenu li span.print {
background-position: 0 -1436px;
}
ul.toolbarmenu li span.download {
background-position: 0 -1412px;
}
ul.toolbarmenu li span.edit {
background-position: 0 -1388px;
}
ul.toolbarmenu li span.viewsource {
background-position: 0 -1460px;
}
ul.toolbarmenu li span.extwin {
background-position: 0 -1484px;
}
ul.toolbarmenu li span.conversation {
background-position: 0 -1532px;
}
ul.toolbarmenu li span.move {
background-position: 0 -2126px;
}
ul.toolbarmenu li span.copy {
background-position: 0 -2150px;
}
#snippetslist {
max-width: 200px;
}
#snippetslist li a {
overflow: hidden;
text-overflow: ellipsis;
}
#rcmKSearchpane {
border-radius: 0 0 4px 4px;
border-top: 0;
}
#rcmKSearchpane ul li {
text-shadow: 0px 1px 1px #333;
text-decoration: none;
min-height: 14px;
padding: 6px 10px 6px 10px;
border: 0;
cursor: default;
}
.popupdialog {
display: none;
padding: 10px;
}
.popupdialog .formbuttons {
margin: 20px 0 4px 0;
}
.ui-dialog .prompt input {
display: block;
margin: 8px 0;
}
.hint {
margin: 4px 0;
color: #999;
text-shadow: 0px 1px 1px #fff;
}
.splitter {
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
position: absolute;
background: url(images/splitter.png) center no-repeat;
}
.splitter-h {
height: 10px;
width: 100%;
cursor: n-resize;
cursor: row-resize;
background-position: center 0;
}
.splitter-v {
width: 10px;
height: 100%;
cursor: e-resize;
cursor: col-resize;
background-position: 0 center;
}
#rcmdraglayer {
min-width: 260px;
width: auto !important;
width: 260px;
padding: 6px 8px;
background: #444;
border: 1px solid #555;
border-radius: 4px;
box-shadow: 0 2px 6px 0 #333;
-moz-box-shadow: 0 2px 6px 0 #333;
-webkit-box-shadow: 0 2px 6px 0 #333;
-o-box-shadow: 0 2px 6px 0 #333;
z-index: 250;
color: #ccc;
white-space: nowrap;
opacity: 0.92;
filter: alpha(opacity=92);
text-shadow: 0px 1px 1px #333;
}
#rcmdraglayer:after {
content: "";
position: absolute;
top: 6px;
left: -6px;
border-style: solid;
border-width: 6px 6px 6px 0;
border-color: transparent #444;
/* reduce the damage in FF3.0 */
display: block;
width: 0;
z-index: 251;
}
.draglayercopy:before {
position: absolute;
bottom: -6px;
left: -6px;
content: " ";
width: 16px;
height: 16px;
background: url(images/buttons.png) -7px -358px no-repeat;
z-index: 255;
}
/*** folder selector ***/
#folder-selector {
z-index: 1000;
}
#folder-selector li a span {
background: url("images/listicons.png") 4px -2021px no-repeat;
display: block;
height: 17px;
min-height: 14px;
padding: 4px 4px 1px 28px;
overflow: hidden;
max-width: 120px;
text-overflow: ellipsis;
}
#folder-selector li a.virtual {
opacity: .2;
}
#folder-selector li a.inbox span {
background-position: 4px -2049px;
}
#folder-selector li a.drafts span {
background-position: 4px -1388px;
}
#folder-selector li a.sent span {
background-position: 4px -2074px;
}
#folder-selector li a.trash span {
background-position: 4px -1508px;
}
#folder-selector li a.junk span {
background-position: 4px -2100px;
}
/*** attachment list ***/
.attachmentslist {
list-style: none;
margin: 0;
padding: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.attachmentslist li {
display: block;
position: relative;
background: url(images/filetypes.png) 0 0 no-repeat;
margin-bottom: 1px;
}
+.attachmentslist li.txt,
+.attachmentslist li.text {
+ background-position: 0 -416px;
+}
+
.attachmentslist li.pdf {
background-position: 0 -26px;
}
.attachmentslist li.doc,
.attachmentslist li.docx,
.attachmentslist li.msword {
background-position: 0 -52px;
}
.attachmentslist li.odt {
background-position: 0 -78px;
}
.attachmentslist li.xls,
.attachmentslist li.xlsx,
.attachmentslist li.msexcel {
background-position: 0 -104px;
}
.attachmentslist li.ods {
background-position: 0 -130px;
}
.attachmentslist li.zip,
.attachmentslist li.gz {
background-position: 0 -156px;
}
.attachmentslist li.rar {
background-position: 0 -182px;
}
.attachmentslist li.image {
background-position: 0 -208px;
}
.attachmentslist li.jpg,
.attachmentslist li.jpeg {
background-position: 0 -234px;
}
.attachmentslist li.png {
background-position: 0 -260px;
}
.attachmentslist li.m4p {
background-position: 0 -286px;
}
.attachmentslist li.mp3,
.attachmentslist li.audio {
background-position: 0 -312px;
}
.attachmentslist li.video {
background-position: 0 -338px;
}
-.attachmentslist li.txt,
-.attachmentslist li.text {
- background-position: 0 -416px;
-}
-
.attachmentslist li.ics,
.attachmentslist li.calendar {
background-position: 0 -364px;
}
.attachmentslist li.vcard {
background-position: 0 -390px;
}
.attachmentslist li.sig,
.attachmentslist li.pgp-signature,
.attachmentslist li.pkcs7-signature {
background-position: 0 -442px;
}
.attachmentslist li.html {
background-position: 0 -468px;
}
.attachmentslist li.eml,
.attachmentslist li.rfc822 {
background-position: 0 -494px;
}
.attachmentslist li.ppt,
.attachmentslist li.pptx,
.attachmentslist li.ppsx,
.attachmentslist li.vnd.mspowerpoint {
background-position: 0 -520px;
}
.attachmentslist li.odp,
.attachmentslist li.otp {
background-position: 0 -546px;
}
.attachmentslist li a,
#compose-attachments ul li {
display: block;
color: #333;
font-weight: bold;
padding: 3px 15px 3px 30px;
text-shadow: 0px 1px 1px #fff;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 20px;
}
.attachmentslist li a.drop {
background: url(images/buttons.png) no-repeat scroll center -1570px;
width: 14px;
height: 20px;
cursor: pointer;
position: absolute;
right: 0;
top: 0;
padding: 0;
}
#compose-attachments ul li {
padding-right: 28px;
}
.attachmentslist li a:hover {
text-decoration: underline;
}
.attachmentslist li.uploading {
background: url(images/ajaxloader.gif) 2px 6px no-repeat;
}
.attachmentslist li a.delete,
.attachmentslist li a.cancelupload {
position: absolute;
top: 4px;
right: 0;
width: 20px;
height: 18px;
padding: 0;
text-decoration: none;
text-indent: -5000px;
background: url(images/buttons.png) -7px -337px no-repeat;
}
.attachmentslist li a.cancelupload {
background-position: -7px -377px;
}
/*** fieldset tabs ***/
.tabsbar {
margin-bottom: 12px;
padding-top: 15px;
height: 27px;
white-space: nowrap;
}
.ui-dialog-content .tabsbar {
margin-bottom: 0;
}
.tabsbar .tablink {
padding: 15px 1px 15px 0;
background: #f8f8f8;
background: -moz-linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(50%,#d3d3d3), color-stop(100%,#f8f8f8));
background: -webkit-linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%);
background: -o-linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%);
background: -ms-linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%);
background: linear-gradient(top, #f8f8f8 0%, #d3d3d3 50%, #f8f8f8 100%);
}
.tabsbar .tablink:last-child {
background: none;
}
.tabsbar .tablink:last-child a {
border-right: 0;
}
.tabsbar .tablink a {
padding: 15px;
color: #999;
font-size: 12px;
font-weight: bold;
text-decoration: none;
background: #fff;
border-right: 1px solid #fafafa;
}
.tabsbar .tablink.selected a {
color: #004458;
background: #f6f6f6;
background: -moz-linear-gradient(top, #fff 40%, #efefef 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(40%,#fff), color-stop(100%,#efefef));
background: -o-linear-gradient(top, #fff 40%, #efefef 100%);
background: -ms-linear-gradient(top, #fff 40%, #efefef 100%);
background: linear-gradient(top, #fff 40%, #efefef 100%);
}
fieldset.tab {
border: 0;
padding: 0;
margin-left: 0;
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Aug 18, 12:38 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1261731
Default Alt Text
(360 KB)

Event Timeline