Page MenuHomePhorge

No OneTemporary

Size
521 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 9f8f6cc7b..09c87088f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,1242 +1,1243 @@
CHANGELOG Roundcube Webmail
===========================
+- Add separate pagesize setting for mail messages and contacts (#1488269)
- 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)
- 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)
- TinyMCE updated to 3.4.6
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 (#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 similiar 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)
- Added About tab in Settings
- 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 (#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
- Security: prevent from relaying malicious requests through modcss.inc
- 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
- 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 as reported by Julien Cayssol
- 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 occured (#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/main.inc.php.dist b/config/main.inc.php.dist
index 32d8a0da0..691c5be9e 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -1,802 +1,805 @@
<?php
/*
+-----------------------------------------------------------------------+
| Main configuration file |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
*/
$rcmail_config = array();
// ----------------------------------
// LOGGING/DEBUGGING
// ----------------------------------
// system error reporting: 1 = log; 2 = report (not implemented yet), 4 = show, 8 = trace
$rcmail_config['debug_level'] = 1;
// log driver: 'syslog' or 'file'.
$rcmail_config['log_driver'] = 'file';
// date format for log entries
// (read http://php.net/manual/en/function.date.php for all format characters)
$rcmail_config['log_date_format'] = 'd-M-Y H:i:s O';
// Syslog ident string to use, if using the 'syslog' log driver.
$rcmail_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
$rcmail_config['syslog_facility'] = LOG_USER;
// Log sent messages to <log_dir>/sendmail or to syslog
$rcmail_config['smtp_log'] = true;
// Log successful logins to <log_dir>/userlogins or to syslog
$rcmail_config['log_logins'] = false;
// Log session authentication errors to <log_dir>/session or to syslog
$rcmail_config['log_session'] = false;
// Log SQL queries to <log_dir>/sql or to syslog
$rcmail_config['sql_debug'] = false;
// Log IMAP conversation to <log_dir>/imap or to syslog
$rcmail_config['imap_debug'] = false;
// Log LDAP conversation to <log_dir>/ldap or to syslog
$rcmail_config['ldap_debug'] = false;
// Log SMTP conversation to <log_dir>/smtp or to syslog
$rcmail_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 - http hostname ($_SERVER['SERVER_NAME'])
// %d - domain (http hostname without the first part)
// %s - domain name after the '@' from e-mail address provided at login screen
// For example %n = mail.domain.tld, %d = domain.tld
$rcmail_config['default_host'] = '';
// TCP port used for IMAP connections
$rcmail_config['default_port'] = 143;
// IMAP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or empty to use
// best server supported one)
$rcmail_config['imap_auth_type'] = null;
// If you know your imap's folder delimiter, you can specify it here.
// Otherwise it will be determined automatically
$rcmail_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
$rcmail_config['imap_ns_personal'] = null;
$rcmail_config['imap_ns_other'] = null;
$rcmail_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.
$rcmail_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.
$rcmail_config['imap_force_lsub'] = false;
// IMAP connection timeout, in seconds. Default: 0 (no limit)
$rcmail_config['imap_timeout'] = 0;
// Optional IMAP authentication identifier to be used as authorization proxy
$rcmail_config['imap_auth_cid'] = null;
// Optional IMAP authentication password to be used for imap_auth_cid
$rcmail_config['imap_auth_pw'] = null;
// Type of IMAP indexes cache. Supported values: 'db', 'apc' and 'memcache'.
$rcmail_config['imap_cache'] = null;
// Enables messages cache. Only 'db' cache is supported.
$rcmail_config['messages_cache'] = false;
// ----------------------------------
// 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 - 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, %d = domain.tld
$rcmail_config['smtp_server'] = '';
// SMTP port (default is 25; 465 for SSL)
$rcmail_config['smtp_port'] = 25;
// SMTP username (if required) if you use %u as the username Roundcube
// will use the current username for login
$rcmail_config['smtp_user'] = '';
// SMTP password (if required) if you use %p as the password Roundcube
// will use the current user's password for login
$rcmail_config['smtp_pass'] = '';
// SMTP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or empty to use
// best server supported one)
$rcmail_config['smtp_auth_type'] = '';
// Optional SMTP authentication identifier to be used as authorization proxy
$rcmail_config['smtp_auth_cid'] = null;
// Optional SMTP authentication password to be used for smtp_auth_cid
$rcmail_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.
$rcmail_config['smtp_helo_host'] = '';
// SMTP connection timeout, in seconds. Default: 0 (no limit)
$rcmail_config['smtp_timeout'] = 0;
// ----------------------------------
// 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!
$rcmail_config['enable_installer'] = false;
// use this folder to store log files (must be writeable for apache user)
// This is used by the 'file' log driver.
$rcmail_config['log_dir'] = 'logs/';
// use this folder to store temp files (must be writeable for apache user)
$rcmail_config['temp_dir'] = 'temp/';
// lifetime of message cache
// possible units: s, m, h, d, w
$rcmail_config['message_cache_lifetime'] = '10d';
// 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
$rcmail_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
$rcmail_config['use_https'] = false;
// Allow browser-autocompletion on login form.
// 0 - disabled, 1 - username and host only, 2 - username, host, password
$rcmail_config['login_autocomplete'] = 0;
// If users authentication is not case sensitive this must be enabled.
// You can also use it to force conversion of logins to lower case.
// After enabling it all user records need to be updated, e.g. with query:
// UPDATE users SET username = LOWER(username);
$rcmail_config['login_lc'] = false;
// 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
$rcmail_config['auto_create_user'] = true;
// provide an URL where a user can get support for this Roundcube installation
// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
$rcmail_config['support_url'] = '';
// replace Roundcube logo with this image
// specify an URL relative to the document root of this Roundcube installation
$rcmail_config['skin_logo'] = null;
// Includes should be interpreted as PHP files
$rcmail_config['skin_include_php'] = false;
// Session lifetime in minutes
// must be greater than 'keep_alive'/60
$rcmail_config['session_lifetime'] = 10;
// session domain: .example.org
$rcmail_config['session_domain'] = '';
// session name. Default: 'roundcube_sessid'
$rcmail_config['session_name'] = null;
// Backend to use for session storage. Can either be 'db' (default) or 'memcache'
// 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
$rcmail_config['session_storage'] = 'db';
// Use these hosts for accessing memcached
// Define any number of hosts in the form hostname:port
$rcmail_config['memcache_hosts'] = null; // e.g. array( 'localhost:11211', '192.168.1.12:11211' );
// check client IP in session athorization
$rcmail_config['ip_check'] = false;
// check referer of incoming requests
$rcmail_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
$rcmail_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.
$rcmail_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 - 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, %d = domain.tld
$rcmail_config['username_domain'] = '';
// 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, %d = domain.tld
$rcmail_config['mail_domain'] = '';
// Password charset.
// Use it if your authentication backend doesn't support UTF-8.
// Defaults to ISO-8859-1 for backward compatibility
$rcmail_config['password_charset'] = 'ISO-8859-1';
// How many seconds must pass between emails sent by a user
$rcmail_config['sendmail_delay'] = 0;
// Maximum number of recipients per message. Default: 0 (no limit)
$rcmail_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
$rcmail_config['max_group_members'] = 0;
// add this user-agent to message headers when sending
$rcmail_config['useragent'] = 'Roundcube Webmail/'.RCMAIL_VERSION;
// use this name to compose page titles
$rcmail_config['product_name'] = 'Roundcube Webmail';
// try to load host-specific configuration
// see http://trac.roundcube.net/wiki/Howto_Config for more details
$rcmail_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
$rcmail_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
$rcmail_config['generic_message_footer_html'] = '';
// add a received header to outgoing mails containing the creators IP and hostname
$rcmail_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.
$rcmail_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
$rcmail_config['mail_header_delimiter'] = NULL;
// number of chars allowed for line when wrapping text.
// text wrapping is done when composing/sending messages
$rcmail_config['line_length'] = 72;
// send plaintext messages as format=flowed
$rcmail_config['send_format_flowed'] = true;
// don't allow these settings to be overriden by the user
$rcmail_config['dont_override'] = array();
// 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
$rcmail_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'
$rcmail_config['client_mimetypes'] = null; # null == default
// mime magic database
$rcmail_config['mime_magic'] = '/usr/share/misc/magic';
// path to imagemagick identify binary
$rcmail_config['im_identify_path'] = null;
// path to imagemagick convert binary
$rcmail_config['im_convert_path'] = null;
// maximum size of uploaded contact photos in pixel
$rcmail_config['contact_photo_size'] = 160;
// Enable DNS checking for e-mail address validation
$rcmail_config['email_dns_check'] = false;
// ----------------------------------
// PLUGINS
// ----------------------------------
// List of active plugins (in plugins/ directory)
$rcmail_config['plugins'] = array();
// ----------------------------------
// USER INTERFACE
// ----------------------------------
// default messages sort column. Use empty value for default server's sorting,
// or 'arrival', 'date', 'subject', 'from', 'to', 'size', 'cc'
$rcmail_config['message_sort_col'] = '';
// default messages sort order
$rcmail_config['message_sort_order'] = 'DESC';
// These cols are shown in the message list. Available cols are:
// subject, from, to, cc, replyto, date, size, status, flag, attachment, 'priority'
$rcmail_config['list_cols'] = array('subject', 'status', 'from', '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
$rcmail_config['language'] = null;
// use this format for date display (date or strftime format)
$rcmail_config['date_format'] = 'Y-m-d';
// give this choice of date formats to the user to select from
$rcmail_config['date_formats'] = array('Y-m-d', 'd-m-Y', 'Y/m/d', 'm/d/Y', 'd/m/Y', 'd.m.Y', 'j.n.Y');
// use this format for time display (date or strftime format)
$rcmail_config['time_format'] = 'H:i';
// give this choice of time formats to the user to select from
$rcmail_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)
$rcmail_config['date_short'] = 'D H:i';
// use this format for detailed date/time formatting (derived from date_format and time_format)
$rcmail_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)
$rcmail_config['drafts_mbox'] = 'Drafts';
// store spam messages in this mailbox
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$rcmail_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)
$rcmail_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)
$rcmail_config['trash_mbox'] = 'Trash';
// display these folders separately in the mailbox list.
// these folders will also be displayed with localized names
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash');
// automatically create the above listed default folders on first login
$rcmail_config['create_default_folders'] = false;
// protect the default folders from renames, deletes, and subscription changes
$rcmail_config['protect_default_folders'] = true;
// if in your system 0 quota means no limit set this option to true
$rcmail_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
$rcmail_config['enable_spellcheck'] = true;
// Enables spellchecker exceptions dictionary.
// Setting it to 'shared' will make the dictionary shared by all users.
$rcmail_config['spellcheck_dictionary'] = false;
// Set the spell checking engine. 'googie' is the default. 'pspell' is also available,
// but requires the Pspell extensions. When using Nox Spell Server, also set 'googie' here.
$rcmail_config['spellcheck_engine'] = 'googie';
// For a locally installed Nox Spell Server, please specify the URI to call it.
// Get Nox Spell Server from http://orangoo.com/labs/?page_id=72
// Leave empty to use the Google spell checking service, what means
// that the message content will be sent to Google in order to check spelling
$rcmail_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.
$rcmail_config['spellcheck_languages'] = NULL;
// Makes that words with all letters capitalized will be ignored (e.g. GOOGLE)
$rcmail_config['spellcheck_ignore_caps'] = false;
// Makes that words with numbers will be ignored (e.g. g00gle)
$rcmail_config['spellcheck_ignore_nums'] = false;
// Makes that words with symbols will be ignored (e.g. g@@gle)
$rcmail_config['spellcheck_ignore_syms'] = false;
// Use this char/string to separate recipients when composing a new message
$rcmail_config['recipients_separator'] = ',';
// don't let users set pagesize to more than this value if set
$rcmail_config['max_pagesize'] = 200;
// Minimal value of user's 'keep_alive' setting (in seconds)
// Must be less than 'session_lifetime'
$rcmail_config['min_keep_alive'] = 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.
$rcmail_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.
$rcmail_config['undo_timeout'] = 0;
// ----------------------------------
// ADDRESSBOOK SETTINGS
// ----------------------------------
// This indicates which type of address book to use. Possible choises:
// 'sql' (default) and 'ldap'.
// 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.
$rcmail_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_
$rcmail_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
*
$rcmail_config['ldap_public']['Verisign'] = array(
'name' => 'Verisign.com',
// Replacement variables supported in host names:
// %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, %d = domain.tld
'hosts' => array('directory.verisign.com'),
'port' => 389,
'use_tls' => false,
'ldap_version' => 3, // using LDAPv3
'user_specific' => false, // If true the base_dn, bind_dn and bind_pass default to the user's IMAP login.
// %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' => '',
// 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' => 'mail',
// 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
'fieldmap' => array(
// Roundcube => LDAP
'name' => 'cn',
'surname' => 'sn',
'firstname' => 'givenName',
'email' => 'mail',
'phone:home' => 'homePhone',
'phone:work' => 'telephoneNumber',
'phone:mobile' => 'mobile',
'street' => 'street',
'zipcode' => 'postalCode',
'locality' => 'l',
'country' => 'c',
'organization' => 'o',
),
'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)
'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.
'referrals' => true|false, // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups
// 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' => '',
'filter' => '(objectClass=groupOfNames)',
'object_classes' => array("top", "groupOfNames"),
'member_attr' => 'member', // name of the member attribute, e.g. uniqueMember
'name_attr' => 'cn', // attribute to be used as group name
),
);
*/
// An ordered array of the ids of the addressbooks that should be searched
// when populating address autocomplete fields server-side. ex: array('sql','Verisign');
$rcmail_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
$rcmail_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.
$rcmail_config['autocomplete_threads'] = 0;
// Max. numer of entries in autocomplete popup. Default: 15.
$rcmail_config['autocomplete_max'] = 15;
// show address fields in this order
// available placeholders: {street}, {locality}, {zipcode}, {country}, {region}
$rcmail_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
$rcmail_config['addressbook_search_mode'] = 0;
// ----------------------------------
// USER PREFERENCES
// ----------------------------------
// Use this charset as fallback for message decoding
$rcmail_config['default_charset'] = 'ISO-8859-1';
// skin name: folder from skins/
$rcmail_config['skin'] = 'default';
-// show up to X items in list view
-$rcmail_config['pagesize'] = 40;
+// show up to X items in messages list view
+$rcmail_config['mail_pagesize'] = 50;
+
+// show up to X items in contacts list view
+$rcmail_config['addressbook_pagesize'] = 50;
// use this timezone to display date/time
$rcmail_config['timezone'] = 'auto';
// is daylight saving On? Default: (bool)date('I');
$rcmail_config['dst_active'] = null;
// prefer displaying HTML messages
$rcmail_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
$rcmail_config['show_images'] = 0;
// compose html formatted messages by default
// 0 - never, 1 - always, 2 - on reply to HTML message only
$rcmail_config['htmleditor'] = 0;
// show pretty dates as standard
$rcmail_config['prettydate'] = true;
// save compose message every 300 seconds (5min)
$rcmail_config['draft_autosave'] = 300;
// default setting if preview pane is enabled
$rcmail_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
$rcmail_config['preview_pane_mark_read'] = 0;
// Clear Trash on logout
$rcmail_config['logout_purge'] = false;
// Compact INBOX on logout
$rcmail_config['logout_expunge'] = false;
// Display attached images below the message body
$rcmail_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
$rcmail_config['mime_param_folding'] = 1;
// Set true if deleted messages should not be displayed
// This will make the application run slower
$rcmail_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
$rcmail_config['read_when_deleted'] = true;
// Set to true to never delete messages immediately
// Use 'Purge' to remove messages marked as deleted
$rcmail_config['flag_for_deletion'] = false;
// Default interval for keep-alive/check-recent requests (in seconds)
// Must be greater than or equal to 'min_keep_alive' and less than 'session_lifetime'
$rcmail_config['keep_alive'] = 60;
// If true all folders will be checked for recent messages
$rcmail_config['check_all_folders'] = false;
// If true, after message delete/move, the next message will be displayed
$rcmail_config['display_next'] = false;
// 0 - Do not expand threads
// 1 - Expand all threads automatically
// 2 - Expand only threads with unread messages
$rcmail_config['autoexpand_threads'] = 0;
// When replying place cursor above original message (top posting)
$rcmail_config['top_posting'] = false;
// When replying strip original signature from message
$rcmail_config['strip_existing_sig'] = true;
// Show signature:
// 0 - Never
// 1 - Always
// 2 - New messages only
// 3 - Forwards and Replies only
$rcmail_config['show_sig'] = 1;
// When replying or forwarding place sender's signature above existing message
$rcmail_config['sig_above'] = false;
// Use MIME encoding (quoted-printable) for 8bit characters in message body
$rcmail_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 $rcmail_config['default_imap_folders']
$rcmail_config['search_mods'] = null; // Example: array('*' => array('subject'=>1, 'from'=>1), 'Sent' => array('subject'=>1, 'to'=>1));
// Defaults of the addressbook search field configuration.
$rcmail_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.
$rcmail_config['delete_always'] = 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
$rcmail_config['mdn_requests'] = 0;
// Return receipt checkbox default state
$rcmail_config['mdn_default'] = 0;
// Delivery Status Notification checkbox default state
$rcmail_config['dsn_default'] = 0;
// Place replies in the folder of the message being replied to
$rcmail_config['reply_same_folder'] = false;
// Sets default mode of Forward feature to "forward as attachment"
$rcmail_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.
$rcmail_config['default_addressbook'] = null;
// Enables spell checking before sending a message.
$rcmail_config['spellcheck_before_send'] = false;
// Skip alternative email addresses in autocompletion (show one address per contact)
$rcmail_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
$rcmail_config['default_font'] = '';
// end of config file
diff --git a/installer/config.php b/installer/config.php
index dabc478c9..b71e3d5d5 100644
--- a/installer/config.php
+++ b/installer/config.php
@@ -1,634 +1,653 @@
<form action="index.php" method="post">
<input type="hidden" name="_step" value="2" />
<?php
// also load the default config to fill in the fields
$RCI->load_defaults();
// register these boolean fields
$RCI->bool_config_props = array(
'ip_check' => 1,
'enable_caching' => 1,
'enable_spellcheck' => 1,
'auto_create_user' => 1,
'smtp_log' => 1,
'prefer_html' => 1,
'preview_pane' => 1,
'debug_level' => 1,
);
// allow the current user to get to the next step
$_SESSION['allowinstaller'] = true;
if (!empty($_POST['submit'])) {
echo '<p class="notice">Copy or download the following configurations and save them in two files';
echo ' (names above the text box) within the <tt>'.RCMAIL_CONFIG_DIR.'</tt> directory of your Roundcube installation.<br/>';
echo ' Make sure that there are no characters outside the <tt>&lt;?php ?&gt;</tt> brackets when saving the files.</p>';
$textbox = new html_textarea(array('rows' => 16, 'cols' => 60, 'class' => "configfile"));
echo '<div><em>main.inc.php (<a href="index.php?_getfile=main">download</a>)</em></div>';
echo $textbox->show(($_SESSION['main.inc.php'] = $RCI->create_config('main')));
echo '<div style="margin-top:1em"><em>db.inc.php (<a href="index.php?_getfile=db">download</a>)</em></div>';
echo $textbox->show($_SESSION['db.inc.php'] = $RCI->create_config('db'));
echo '<p class="hint">Of course there are more options to configure.
Have a look at the config files or visit <a href="http://trac.roundcube.net/wiki/Howto_Config">Howto_Config</a> to find out.</p>';
echo '<p><input type="button" onclick="location.href=\'./index.php?_step=3\'" value="CONTINUE" /></p>';
// echo '<style type="text/css"> .configblock { display:none } </style>';
echo "\n<hr style='margin-bottom:1.6em' />\n";
}
?>
<fieldset>
<legend>General configuration</legend>
<dl class="configblock">
<dt class="propname">product_name</dt>
<dd>
<?php
$input_prodname = new html_inputfield(array('name' => '_product_name', 'size' => 30, 'id' => "cfgprodname"));
echo $input_prodname->show($RCI->getprop('product_name'));
?>
<div>The name of your service (used to compose page titles)</div>
</dd>
<dt class="propname">temp_dir</dt>
<dd>
<?php
$input_tempdir = new html_inputfield(array('name' => '_temp_dir', 'size' => 30, 'id' => "cfgtempdir"));
echo $input_tempdir->show($RCI->getprop('temp_dir'));
?>
<div>Use this folder to store temp files (must be writeable for webserver)</div>
</dd>
<dt class="propname">ip_check</dt>
<dd>
<?php
$check_ipcheck = new html_checkbox(array('name' => '_ip_check', 'id' => "cfgipcheck"));
echo $check_ipcheck->show(intval($RCI->getprop('ip_check')), array('value' => 1));
?>
<label for="cfgipcheck">Check client IP in session authorization</label><br />
<p class="hint">This increases security but can cause sudden logouts when someone uses a proxy with changing IPs.</p>
</dd>
<dt class="propname">des_key</dt>
<dd>
<?php
$input_deskey = new html_inputfield(array('name' => '_des_key', 'size' => 30, 'id' => "cfgdeskey"));
echo $input_deskey->show($RCI->getprop('des_key'));
?>
<div>This key is used to encrypt the users imap password before storing in the session record</div>
<p class="hint">It's a random generated string to ensure that every installation has its own key.
If you enter it manually please provide a string of exactly 24 chars.</p>
</dd>
<dt class="propname">enable_caching</dt>
<dd>
<?php
$check_caching = new html_checkbox(array('name' => '_enable_caching', 'id' => "cfgcache"));
echo $check_caching->show(intval($RCI->getprop('enable_caching')), array('value' => 1));
?>
<label for="cfgcache">Cache messages in local database</label><br />
</dd>
<dt class="propname">enable_spellcheck</dt>
<dd>
<?php
$check_spell = new html_checkbox(array('name' => '_enable_spellcheck', 'id' => "cfgspellcheck"));
echo $check_spell->show(intval($RCI->getprop('enable_spellcheck')), array('value' => 1));
?>
<label for="cfgspellcheck">Make use of the spell checker</label><br />
</dd>
<dt class="propname">spellcheck_engine</dt>
<dd>
<?php
$select_spell = new html_select(array('name' => '_spellcheck_engine', 'id' => "cfgspellcheckengine"));
if (extension_loaded('pspell'))
$select_spell->add('pspell', 'pspell');
$select_spell->add('Googie', 'googie');
echo $select_spell->show($RCI->is_post ? $_POST['_spellcheck_engine'] : 'pspell');
?>
<label for="cfgspellcheckengine">Which spell checker to use</label><br />
<p class="hint">GoogieSpell implies that the message content will be sent to Google in order to check the spelling.</p>
</dd>
<dt class="propname">identities_level</dt>
<dd>
<?php
$input_ilevel = new html_select(array('name' => '_identities_level', 'id' => "cfgidentitieslevel"));
$input_ilevel->add('many identities with possibility to edit all params', 0);
$input_ilevel->add('many identities with possibility to edit all params but not email address', 1);
$input_ilevel->add('one identity with possibility to edit all params', 2);
$input_ilevel->add('one identity with possibility to edit all params but not email address', 3);
echo $input_ilevel->show($RCI->getprop('identities_level'), 0);
?>
<div>Level of identities access</div>
<p class="hint">Defines what users can do with their identities.</p>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>Logging & Debugging</legend>
<dl class="loggingblock">
<dt class="propname">debug_level</dt>
<dd>
<?php
$value = $RCI->getprop('debug_level');
$check_debug = new html_checkbox(array('name' => '_debug_level[]'));
echo $check_debug->show(($value & 1) ? 1 : 0 , array('value' => 1, 'id' => 'cfgdebug1'));
echo '<label for="cfgdebug1">Log errors</label><br />';
echo $check_debug->show(($value & 4) ? 4 : 0, array('value' => 4, 'id' => 'cfgdebug4'));
echo '<label for="cfgdebug4">Print errors (to the browser)</label><br />';
echo $check_debug->show(($value & 8) ? 8 : 0, array('value' => 8, 'id' => 'cfgdebug8'));
echo '<label for="cfgdebug8">Verbose display (enables debug console)</label><br />';
?>
</dd>
<dt class="propname">log_driver</dt>
<dd>
<?php
$select_log_driver = new html_select(array('name' => '_log_driver', 'id' => "cfglogdriver"));
$select_log_driver->add(array('file', 'syslog'), array('file', 'syslog'));
echo $select_log_driver->show($RCI->getprop('log_driver', 'file'));
?>
<div>How to do logging? 'file' - write to files in the log directory, 'syslog' - use the syslog facility.</div>
</dd>
<dt class="propname">log_dir</dt>
<dd>
<?php
$input_logdir = new html_inputfield(array('name' => '_log_dir', 'size' => 30, 'id' => "cfglogdir"));
echo $input_logdir->show($RCI->getprop('log_dir'));
?>
<div>Use this folder to store log files (must be writeable for webserver). Note that this only applies if you are using the 'file' log_driver.</div>
</dd>
<dt class="propname">syslog_id</dt>
<dd>
<?php
$input_syslogid = new html_inputfield(array('name' => '_syslog_id', 'size' => 30, 'id' => "cfgsyslogid"));
echo $input_syslogid->show($RCI->getprop('syslog_id', 'roundcube'));
?>
<div>What ID to use when logging with syslog. Note that this only applies if you are using the 'syslog' log_driver.</div>
</dd>
<dt class="propname">syslog_facility</dt>
<dd>
<?php
$input_syslogfacility = new html_select(array('name' => '_syslog_facility', 'id' => "cfgsyslogfacility"));
$input_syslogfacility->add('user-level messages', LOG_USER);
$input_syslogfacility->add('mail subsystem', LOG_MAIL);
$input_syslogfacility->add('local level 0', LOG_LOCAL0);
$input_syslogfacility->add('local level 1', LOG_LOCAL1);
$input_syslogfacility->add('local level 2', LOG_LOCAL2);
$input_syslogfacility->add('local level 3', LOG_LOCAL3);
$input_syslogfacility->add('local level 4', LOG_LOCAL4);
$input_syslogfacility->add('local level 5', LOG_LOCAL5);
$input_syslogfacility->add('local level 6', LOG_LOCAL6);
$input_syslogfacility->add('local level 7', LOG_LOCAL7);
echo $input_syslogfacility->show($RCI->getprop('syslog_facility'), LOG_USER);
?>
<div>What ID to use when logging with syslog. Note that this only applies if you are using the 'syslog' log_driver.</div>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>Database setup</legend>
<dl class="configblock" id="cgfblockdb">
<dt class="propname">db_dsnw</dt>
<dd>
<p>Database settings for read/write operations:</p>
<?php
require_once 'MDB2.php';
$supported_dbs = array('MySQL' => 'mysql', 'MySQLi' => 'mysqli',
'PgSQL' => 'pgsql', 'SQLite' => 'sqlite');
$select_dbtype = new html_select(array('name' => '_dbtype', 'id' => "cfgdbtype"));
foreach ($supported_dbs AS $database => $ext) {
if (extension_loaded($ext)) {
$select_dbtype->add($database, $ext);
}
}
$input_dbhost = new html_inputfield(array('name' => '_dbhost', 'size' => 20, 'id' => "cfgdbhost"));
$input_dbname = new html_inputfield(array('name' => '_dbname', 'size' => 20, 'id' => "cfgdbname"));
$input_dbuser = new html_inputfield(array('name' => '_dbuser', 'size' => 20, 'id' => "cfgdbuser"));
$input_dbpass = new html_passwordfield(array('name' => '_dbpass', 'size' => 20, 'id' => "cfgdbpass"));
$dsnw = MDB2::parseDSN($RCI->getprop('db_dsnw'));
echo $select_dbtype->show($RCI->is_post ? $_POST['_dbtype'] : $dsnw['phptype']);
echo '<label for="cfgdbtype">Database type</label><br />';
echo $input_dbhost->show($RCI->is_post ? $_POST['_dbhost'] : $dsnw['hostspec']);
echo '<label for="cfgdbhost">Database server (omit for sqlite)</label><br />';
echo $input_dbname->show($RCI->is_post ? $_POST['_dbname'] : $dsnw['database']);
echo '<label for="cfgdbname">Database name (use absolute path and filename for sqlite)</label><br />';
echo $input_dbuser->show($RCI->is_post ? $_POST['_dbuser'] : $dsnw['username']);
echo '<label for="cfgdbuser">Database user name (needs write permissions)(omit for sqlite)</label><br />';
echo $input_dbpass->show($RCI->is_post ? $_POST['_dbpass'] : $dsnw['password']);
echo '<label for="cfgdbpass">Database password (omit for sqlite)</label><br />';
?>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>IMAP Settings</legend>
<dl class="configblock" id="cgfblockimap">
<dt class="propname">default_host</dt>
<dd>
<div>The IMAP host(s) chosen to perform the log-in</div>
<div id="defaulthostlist">
<?php
$text_imaphost = new html_inputfield(array('name' => '_default_host[]', 'size' => 30));
$default_hosts = $RCI->get_hostlist();
if (empty($default_hosts))
$default_hosts = array('');
$i = 0;
foreach ($default_hosts as $host) {
echo '<div id="defaulthostentry'.$i.'">' . $text_imaphost->show($host);
if ($i++ > 0)
echo '<a href="#" onclick="removehostfield(this.parentNode);return false" class="removelink" title="Remove this entry">remove</a>';
echo '</div>';
}
?>
</div>
<div><a href="javascript:addhostfield()" class="addlink" title="Add another field">add</a></div>
<p class="hint">Leave blank to show a textbox at login. To use SSL/IMAPS connection, type ssl://hostname</p>
</dd>
<dt class="propname">default_port</dt>
<dd>
<?php
$text_imapport = new html_inputfield(array('name' => '_default_port', 'size' => 6, 'id' => "cfgimapport"));
echo $text_imapport->show($RCI->getprop('default_port'));
?>
<div>TCP port used for IMAP connections</div>
</dd>
<dt class="propname">username_domain</dt>
<dd>
<?php
$text_userdomain = new html_inputfield(array('name' => '_username_domain', 'size' => 30, 'id' => "cfguserdomain"));
echo $text_userdomain->show($RCI->getprop('username_domain'));
?>
<div>Automatically add this domain to user names for login</div>
<p class="hint">Only for IMAP servers that require full e-mail addresses for login</p>
</dd>
<dt class="propname">auto_create_user</dt>
<dd>
<?php
$check_autocreate = new html_checkbox(array('name' => '_auto_create_user', 'id' => "cfgautocreate"));
echo $check_autocreate->show(intval($RCI->getprop('auto_create_user')), array('value' => 1));
?>
<label for="cfgautocreate">Automatically create a new Roundcube user when log-in the first time</label><br />
<p class="hint">A user is authenticated by the IMAP server but it requires a local record to store settings
and contacts. With this option enabled a new user record will automatically be created once the IMAP login succeeds.</p>
<p class="hint">If this option is disabled, the login only succeeds if there's a matching user-record in the local Roundcube database
what means that you have to create those records manually or disable this option after the first login.</p>
</dd>
<dt class="propname">sent_mbox</dt>
<dd>
<?php
$text_sentmbox = new html_inputfield(array('name' => '_sent_mbox', 'size' => 20, 'id' => "cfgsentmbox"));
echo $text_sentmbox->show($RCI->getprop('sent_mbox'));
?>
<div>Store sent messages in this folder</div>
<p class="hint">Leave blank if sent messages should not be stored. Note: folder must include namespace prefix if any.</p>
</dd>
<dt class="propname">trash_mbox</dt>
<dd>
<?php
$text_trashmbox = new html_inputfield(array('name' => '_trash_mbox', 'size' => 20, 'id' => "cfgtrashmbox"));
echo $text_trashmbox->show($RCI->getprop('trash_mbox'));
?>
<div>Move messages to this folder when deleting them</div>
<p class="hint">Leave blank if they should be deleted directly. Note: folder must include namespace prefix if any.</p>
</dd>
<dt class="propname">drafts_mbox</dt>
<dd>
<?php
$text_draftsmbox = new html_inputfield(array('name' => '_drafts_mbox', 'size' => 20, 'id' => "cfgdraftsmbox"));
echo $text_draftsmbox->show($RCI->getprop('drafts_mbox'));
?>
<div>Store draft messages in this folder</div>
<p class="hint">Leave blank if they should not be stored. Note: folder must include namespace prefix if any.</p>
</dd>
<dt class="propname">junk_mbox</dt>
<dd>
<?php
$text_junkmbox = new html_inputfield(array('name' => '_junk_mbox', 'size' => 20, 'id' => "cfgjunkmbox"));
echo $text_junkmbox->show($RCI->getprop('junk_mbox'));
?>
<div>Store spam messages in this folder</div>
<p class="hint">Note: folder must include namespace prefix if any.</p>
</dd>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>SMTP Settings</legend>
<dl class="configblock" id="cgfblocksmtp">
<dt class="propname">smtp_server</dt>
<dd>
<?php
$text_smtphost = new html_inputfield(array('name' => '_smtp_server', 'size' => 30, 'id' => "cfgsmtphost"));
echo $text_smtphost->show($RCI->getprop('smtp_server'));
?>
<div>Use this host for sending mails</div>
<p class="hint">To use SSL connection, set ssl://smtp.host.com. If left blank, the PHP mail() function is used</p>
</dd>
<dt class="propname">smtp_port</dt>
<dd>
<?php
$text_smtpport = new html_inputfield(array('name' => '_smtp_port', 'size' => 6, 'id' => "cfgsmtpport"));
echo $text_smtpport->show($RCI->getprop('smtp_port'));
?>
<div>SMTP port (default is 25; 465 for SSL; 587 for submission)</div>
</dd>
<dt class="propname">smtp_user/smtp_pass</dt>
<dd>
<?php
$text_smtpuser = new html_inputfield(array('name' => '_smtp_user', 'size' => 20, 'id' => "cfgsmtpuser"));
$text_smtppass = new html_passwordfield(array('name' => '_smtp_pass', 'size' => 20, 'id' => "cfgsmtppass"));
echo $text_smtpuser->show($RCI->getprop('smtp_user'));
echo $text_smtppass->show($RCI->getprop('smtp_pass'));
?>
<div>SMTP username and password (if required)</div>
<p>
<?php
$check_smtpuser = new html_checkbox(array('name' => '_smtp_user_u', 'id' => "cfgsmtpuseru"));
echo $check_smtpuser->show($RCI->getprop('smtp_user') == '%u' || $_POST['_smtp_user_u'] ? 1 : 0, array('value' => 1));
?>
<label for="cfgsmtpuseru">Use the current IMAP username and password for SMTP authentication</label>
</p>
</dd>
<!--
<dt class="propname">smtp_auth_type</dt>
<dd>
<?php
/*
$select_smtpauth = new html_select(array('name' => '_smtp_auth_type', 'id' => "cfgsmtpauth"));
$select_smtpauth->add(array('(auto)', 'PLAIN', 'DIGEST-MD5', 'CRAM-MD5', 'LOGIN'), array('0', 'PLAIN', 'DIGEST-MD5', 'CRAM-MD5', 'LOGIN'));
echo $select_smtpauth->show(intval($RCI->getprop('smtp_auth_type')));
*/
?>
<div>Method to authenticate at the SMTP server. Choose (auto) if you don't know what this is</div>
</dd>
-->
<dt class="propname">smtp_log</dt>
<dd>
<?php
$check_smtplog = new html_checkbox(array('name' => '_smtp_log', 'id' => "cfgsmtplog"));
echo $check_smtplog->show(intval($RCI->getprop('smtp_log')), array('value' => 1));
?>
<label for="cfgsmtplog">Log sent messages in <tt>{log_dir}/sendmail</tt> or to syslog.</label><br />
</dd>
</dl>
</fieldset>
<fieldset>
<legend>Display settings &amp; user prefs</legend>
<dl class="configblock" id="cgfblockdisplay">
<dt class="propname">language <span class="userconf">*</span></dt>
<dd>
<?php
$input_locale = new html_inputfield(array('name' => '_language', 'size' => 6, 'id' => "cfglocale"));
echo $input_locale->show($RCI->getprop('language'));
?>
<div>The default locale setting. This also defines the language of the login screen.<br/>Leave it empty to auto-detect the user agent language.</div>
<p class="hint">Enter a <a href="http://www.faqs.org/rfcs/rfc1766">RFC1766</a> formatted language name. Examples: en_US, de_DE, de_CH, fr_FR, pt_BR</p>
</dd>
<dt class="propname">skin <span class="userconf">*</span></dt>
<dd>
<?php
$input_skin = new html_select(array('name' => '_skin', 'id' => "cfgskin"));
$input_skin->add($RCI->list_skins());
echo $input_skin->show($RCI->getprop('skin'));
?>
<div>Name of interface skin (folder in /skins)</div>
</dd>
<dt class="propname">skin_logo</dt>
<dd>
<?php
$input_skin = new html_inputfield(array('name' => '_skin_logo', 'size' => 50, 'id' => "cfgskinlogo"));
echo $input_skin->show($RCI->getprop('skin_logo'));
?>
<div>Custom image to display instead of the Roundcube logo.</div>
<p class="hint">Enter a URL relative to the document root of this Roundcube installation.</p>
</dd>
-<dt class="propname">pagesize <span class="userconf">*</span></dt>
+<dt class="propname">mail_pagesize <span class="userconf">*</span></dt>
<dd>
<?php
-$input_pagesize = new html_inputfield(array('name' => '_pagesize', 'size' => 6, 'id' => "cfgpagesize"));
-echo $input_pagesize->show($RCI->getprop('pagesize'));
+$pagesize = $RCI->getprop('mail_pagesize');
+if (!$pagesize) {
+ $pagesize = $RCI->getprop('pagesize');
+}
+$input_pagesize = new html_inputfield(array('name' => '_mail_pagesize', 'size' => 6, 'id' => "cfgmailpagesize"));
+echo $input_pagesize->show($pagesize);
+
+?>
+<div>Show up to X items in the mail messages list view.</div>
+</dd>
+
+<dt class="propname">addressbook_pagesize <span class="userconf">*</span></dt>
+<dd>
+<?php
+
+$pagesize = $RCI->getprop('addressbook_pagesize');
+if (!$pagesize) {
+ $pagesize = $RCI->getprop('pagesize');
+}
+$input_pagesize = new html_inputfield(array('name' => '_addressbook_pagesize', 'size' => 6, 'id' => "cfgabookpagesize"));
+echo $input_pagesize->show($pagesize);
?>
-<div>Show up to X items in list view.</div>
+<div>Show up to X items in the contacts list view.</div>
</dd>
<dt class="propname">prefer_html <span class="userconf">*</span></dt>
<dd>
<?php
$check_htmlview = new html_checkbox(array('name' => '_prefer_html', 'id' => "cfghtmlview", 'value' => 1));
echo $check_htmlview->show(intval($RCI->getprop('prefer_html')));
?>
<label for="cfghtmlview">Prefer displaying HTML messages</label><br />
</dd>
<dt class="propname">preview_pane <span class="userconf">*</span></dt>
<dd>
<?php
$check_prevpane = new html_checkbox(array('name' => '_preview_pane', 'id' => "cfgprevpane", 'value' => 1));
echo $check_prevpane->show(intval($RCI->getprop('preview_pane')));
?>
<label for="cfgprevpane">If preview pane is enabled</label><br />
</dd>
<dt class="propname">htmleditor <span class="userconf">*</span></dt>
<dd>
<label for="cfghtmlcompose">Compose HTML formatted messages</label>
<?php
$select_htmlcomp = new html_select(array('name' => '_htmleditor', 'id' => "cfghtmlcompose"));
$select_htmlcomp->add('never', 0);
$select_htmlcomp->add('always', 1);
$select_htmlcomp->add('on reply to HTML message only', 2);
echo $select_htmlcomp->show(intval($RCI->getprop('htmleditor')));
?>
</dd>
<dt class="propname">draft_autosave <span class="userconf">*</span></dt>
<dd>
<label for="cfgautosave">Save compose message every</label>
<?php
$select_autosave = new html_select(array('name' => '_draft_autosave', 'id' => 'cfgautosave'));
$select_autosave->add('never', 0);
foreach (array(1, 3, 5, 10) as $i => $min)
$select_autosave->add("$min min", $min*60);
echo $select_autosave->show(intval($RCI->getprop('draft_autosave')));
?>
</dd>
<dt class="propname">mdn_requests <span class="userconf">*</span></dt>
<dd>
<?php
$mdn_opts = array(
0 => 'ask the user',
1 => 'send automatically',
3 => 'send receipt to user contacts, otherwise ask the user',
4 => 'send receipt to user contacts, otherwise ignore',
2 => 'ignore',
);
$select_mdnreq = new html_select(array('name' => '_mdn_requests', 'id' => "cfgmdnreq"));
$select_mdnreq->add(array_values($mdn_opts), array_keys($mdn_opts));
echo $select_mdnreq->show(intval($RCI->getprop('mdn_requests')));
?>
<div>Behavior if a received message requests a message delivery notification (read receipt)</div>
</dd>
<dt class="propname">mime_param_folding <span class="userconf">*</span></dt>
<dd>
<?php
$select_param_folding = new html_select(array('name' => '_mime_param_folding', 'id' => "cfgmimeparamfolding"));
$select_param_folding->add('Full RFC 2231 (Roundcube, Thunderbird)', '0');
$select_param_folding->add('RFC 2047/2231 (MS Outlook, OE)', '1');
$select_param_folding->add('Full RFC 2047 (deprecated)', '2');
echo $select_param_folding->show(intval($RCI->getprop('mime_param_folding')));
?>
<div>How to encode attachment long/non-ascii names</div>
</dd>
</dl>
<p class="hint"><span class="userconf">*</span>&nbsp; These settings are defaults for the user preferences</p>
</fieldset>
<?php
echo '<p><input type="submit" name="submit" value="' . ($RCI->configured ? 'UPDATE' : 'CREATE') . ' CONFIG" ' . ($RCI->failures ? 'disabled' : '') . ' /></p>';
?>
</form>
diff --git a/installer/rcube_install.php b/installer/rcube_install.php
index 737972b93..a3618a515 100644
--- a/installer/rcube_install.php
+++ b/installer/rcube_install.php
@@ -1,793 +1,794 @@
<?php
/*
+-----------------------------------------------------------------------+
| rcube_install.php |
| |
| This file is part of the Roundcube Webmail package |
| Copyright (C) 2008-2011, The Roundcube Dev Team |
| Licensed under the GNU Public License |
+-----------------------------------------------------------------------+
$Id: $
*/
/**
* Class to control the installation process of the Roundcube Webmail package
*
* @category Install
* @package Roundcube
* @author Thomas Bruederli
*/
class rcube_install
{
var $step;
var $is_post = false;
var $failures = 0;
var $config = array();
var $configured = false;
var $last_error = null;
var $db_map = array('pgsql' => 'postgres', 'mysqli' => 'mysql', 'sqlsrv' => 'mssql');
var $email_pattern = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9])';
var $bool_config_props = array();
var $obsolete_config = array('db_backend', 'double_auth');
var $replaced_config = array(
'skin_path' => 'skin',
'locale_string' => 'language',
'multiple_identities' => 'identities_level',
'addrbook_show_images' => 'show_images',
'imap_root' => 'imap_ns_personal',
+ 'pagesize' => 'mail_pagesize',
);
-
+
// these config options are required for a working system
var $required_config = array(
'db_dsnw', 'db_table_contactgroups', 'db_table_contactgroupmembers',
'des_key', 'session_lifetime',
);
-
+
/**
* Constructor
*/
function rcube_install()
{
$this->step = intval($_REQUEST['_step']);
$this->is_post = $_SERVER['REQUEST_METHOD'] == 'POST';
}
/**
* Singleton getter
*/
function get_instance()
{
static $inst;
if (!$inst)
$inst = new rcube_install();
return $inst;
}
/**
* Read the default config files and store properties
*/
function load_defaults()
{
$this->_load_config('.php.dist');
}
/**
* Read the local config files and store properties
*/
function load_config()
{
$this->config = array();
$this->_load_config('.php');
$this->configured = !empty($this->config);
}
/**
* Read the default config file and store properties
* @access private
*/
function _load_config($suffix)
{
if (is_readable($main_inc = RCMAIL_CONFIG_DIR . '/main.inc' . $suffix)) {
include($main_inc);
if (is_array($rcmail_config))
$this->config += $rcmail_config;
}
if (is_readable($db_inc = RCMAIL_CONFIG_DIR . '/db.inc'. $suffix)) {
include($db_inc);
if (is_array($rcmail_config))
$this->config += $rcmail_config;
}
}
/**
* Getter for a certain config property
*
* @param string Property name
* @param string Default value
* @return string The property value
*/
function getprop($name, $default = '')
{
$value = $this->config[$name];
if ($name == 'des_key' && !$this->configured && !isset($_REQUEST["_$name"]))
$value = rcube_install::random_key(24);
return $value !== null && $value !== '' ? $value : $default;
}
/**
* Take the default config file and replace the parameters
* with the submitted form data
*
* @param string Which config file (either 'main' or 'db')
* @return string The complete config file content
*/
function create_config($which, $force = false)
{
$out = @file_get_contents(RCMAIL_CONFIG_DIR . "/{$which}.inc.php.dist");
if (!$out)
return '[Warning: could not read the config template file]';
foreach ($this->config as $prop => $default) {
$is_default = !isset($_POST["_$prop"]);
$value = !$is_default || $this->bool_config_props[$prop] ? $_POST["_$prop"] : $default;
// convert some form data
if ($prop == 'debug_level' && !$is_default) {
if (is_array($value)) {
$val = 0;
foreach ($value as $dbgval)
$val += intval($dbgval);
$value = $val;
}
}
else if ($which == 'db' && $prop == 'db_dsnw' && !empty($_POST['_dbtype'])) {
if ($_POST['_dbtype'] == 'sqlite')
$value = sprintf('%s://%s?mode=0646', $_POST['_dbtype'], $_POST['_dbname']{0} == '/' ? '/' . $_POST['_dbname'] : $_POST['_dbname']);
else if ($_POST['_dbtype'])
$value = sprintf('%s://%s:%s@%s/%s', $_POST['_dbtype'],
rawurlencode($_POST['_dbuser']), rawurlencode($_POST['_dbpass']), $_POST['_dbhost'], $_POST['_dbname']);
}
else if ($prop == 'smtp_auth_type' && $value == '0') {
$value = '';
}
else if ($prop == 'default_host' && is_array($value)) {
$value = rcube_install::_clean_array($value);
if (count($value) <= 1)
$value = $value[0];
}
- else if ($prop == 'pagesize') {
+ else if ($prop == 'mail_pagesize' || $prop == 'addressbook_pagesize') {
$value = max(2, intval($value));
}
else if ($prop == 'smtp_user' && !empty($_POST['_smtp_user_u'])) {
$value = '%u';
}
else if ($prop == 'smtp_pass' && !empty($_POST['_smtp_user_u'])) {
$value = '%p';
}
else if ($prop == 'default_imap_folders') {
$value = array();
foreach ($this->config['default_imap_folders'] as $_folder) {
switch ($_folder) {
case 'Drafts': $_folder = $this->config['drafts_mbox']; break;
case 'Sent': $_folder = $this->config['sent_mbox']; break;
case 'Junk': $_folder = $this->config['junk_mbox']; break;
case 'Trash': $_folder = $this->config['trash_mbox']; break;
}
if (!in_array($_folder, $value))
$value[] = $_folder;
}
}
else if (is_bool($default)) {
$value = (bool)$value;
}
else if (is_numeric($value)) {
$value = intval($value);
}
// skip this property
if (!$force && !$this->configured && ($value == $default))
continue;
// save change
$this->config[$prop] = $value;
// replace the matching line in config file
$out = preg_replace(
'/(\$rcmail_config\[\''.preg_quote($prop).'\'\])\s+=\s+(.+);/Uie',
"'\\1 = ' . rcube_install::_dump_var(\$value, \$prop) . ';'",
$out);
}
return trim($out);
}
/**
* Check the current configuration for missing properties
* and deprecated or obsolete settings
*
* @return array List with problems detected
*/
function check_config()
{
$this->config = array();
$this->load_defaults();
$defaults = $this->config;
$this->load_config();
if (!$this->configured)
return null;
$out = $seen = array();
$required = array_flip($this->required_config);
// iterate over the current configuration
foreach ($this->config as $prop => $value) {
if ($replacement = $this->replaced_config[$prop]) {
$out['replaced'][] = array('prop' => $prop, 'replacement' => $replacement);
$seen[$replacement] = true;
}
else if (!$seen[$prop] && in_array($prop, $this->obsolete_config)) {
$out['obsolete'][] = array('prop' => $prop);
$seen[$prop] = true;
}
}
// iterate over default config
foreach ($defaults as $prop => $value) {
if (!isset($seen[$prop]) && !isset($this->config[$prop]) && isset($required[$prop]))
$out['missing'][] = array('prop' => $prop);
}
// check config dependencies and contradictions
if ($this->config['enable_spellcheck'] && $this->config['spellcheck_engine'] == 'pspell') {
if (!extension_loaded('pspell')) {
$out['dependencies'][] = array('prop' => 'spellcheck_engine',
'explain' => 'This requires the <tt>pspell</tt> extension which could not be loaded.');
}
else if (!empty($this->config['spellcheck_languages'])) {
foreach ($this->config['spellcheck_languages'] as $lang => $descr)
if (!pspell_new($lang))
$out['dependencies'][] = array('prop' => 'spellcheck_languages',
'explain' => "You are missing pspell support for language $lang ($descr)");
}
}
if ($this->config['log_driver'] == 'syslog') {
if (!function_exists('openlog')) {
$out['dependencies'][] = array('prop' => 'log_driver',
'explain' => 'This requires the <tt>sylog</tt> extension which could not be loaded.');
}
if (empty($this->config['syslog_id'])) {
$out['dependencies'][] = array('prop' => 'syslog_id',
'explain' => 'Using <tt>syslog</tt> for logging requires a syslog ID to be configured');
}
}
// check ldap_public sources having global_search enabled
if (is_array($this->config['ldap_public']) && !is_array($this->config['autocomplete_addressbooks'])) {
foreach ($this->config['ldap_public'] as $ldap_public) {
if ($ldap_public['global_search']) {
$out['replaced'][] = array('prop' => 'ldap_public::global_search', 'replacement' => 'autocomplete_addressbooks');
break;
}
}
}
return $out;
}
/**
* Merge the current configuration with the defaults
* and copy replaced values to the new options.
*/
function merge_config()
{
$current = $this->config;
$this->config = array();
$this->load_defaults();
foreach ($this->replaced_config as $prop => $replacement) {
if (isset($current[$prop])) {
if ($prop == 'skin_path')
$this->config[$replacement] = preg_replace('#skins/(\w+)/?$#', '\\1', $current[$prop]);
else if ($prop == 'multiple_identities')
$this->config[$replacement] = $current[$prop] ? 2 : 0;
else
$this->config[$replacement] = $current[$prop];
}
unset($current[$prop]);
}
foreach ($this->obsolete_config as $prop) {
unset($current[$prop]);
}
// add all ldap_public sources having global_search enabled to autocomplete_addressbooks
if (is_array($current['ldap_public'])) {
foreach ($current['ldap_public'] as $key => $ldap_public) {
if ($ldap_public['global_search']) {
$this->config['autocomplete_addressbooks'][] = $key;
unset($current['ldap_public'][$key]['global_search']);
}
}
}
if ($current['keep_alive'] && $current['session_lifetime'] < $current['keep_alive'])
$current['session_lifetime'] = max(10, ceil($current['keep_alive'] / 60) * 2);
$this->config = array_merge($this->config, $current);
foreach ((array)$current['ldap_public'] as $key => $values) {
$this->config['ldap_public'][$key] = $current['ldap_public'][$key];
}
}
/**
* Compare the local database schema with the reference schema
* required for this version of Roundcube
*
* @param boolean True if the schema schould be updated
* @return boolean True if the schema is up-to-date, false if not or an error occured
*/
function db_schema_check($DB, $update = false)
{
if (!$this->configured)
return false;
// read reference schema from mysql.initial.sql
$db_schema = $this->db_read_schema(INSTALL_PATH . 'SQL/mysql.initial.sql');
$errors = array();
// check list of tables
$existing_tables = $DB->list_tables();
foreach ($db_schema as $table => $cols) {
$table = !empty($this->config['db_table_'.$table]) ? $this->config['db_table_'.$table] : $table;
if (!in_array($table, $existing_tables)) {
$errors[] = "Missing table '".$table."'";
}
else { // compare cols
$db_cols = $DB->list_cols($table);
$diff = array_diff(array_keys($cols), $db_cols);
if (!empty($diff))
$errors[] = "Missing columns in table '$table': " . join(',', $diff);
}
}
return !empty($errors) ? $errors : false;
}
/**
* Utility function to read database schema from an .sql file
*/
private function db_read_schema($schemafile)
{
$lines = file($schemafile);
$table_block = false;
$schema = array();
foreach ($lines as $line) {
if (preg_match('/^\s*create table `?([a-z0-9_]+)`?/i', $line, $m)) {
$table_block = $m[1];
}
else if ($table_block && preg_match('/^\s*`?([a-z0-9_-]+)`?\s+([a-z]+)/', $line, $m)) {
$col = $m[1];
if (!in_array(strtoupper($col), array('PRIMARY','KEY','INDEX','UNIQUE','CONSTRAINT','REFERENCES','FOREIGN'))) {
$schema[$table_block][$col] = $m[2];
}
}
}
return $schema;
}
/**
* Compare the local database schema with the reference schema
* required for this version of Roundcube
*
* @param boolean True if the schema schould be updated
* @return boolean True if the schema is up-to-date, false if not or an error occured
*/
function mdb2_schema_check($update = false)
{
if (!$this->configured)
return false;
$options = array(
'use_transactions' => false,
'log_line_break' => "\n",
'idxname_format' => '%s',
'debug' => false,
'quote_identifier' => true,
'force_defaults' => false,
'portability' => true
);
$dsnw = $this->config['db_dsnw'];
$schema = MDB2_Schema::factory($dsnw, $options);
$schema->db->supported['transactions'] = false;
if (PEAR::isError($schema)) {
$this->raise_error(array('code' => $schema->getCode(), 'message' => $schema->getMessage() . ' ' . $schema->getUserInfo()));
return false;
}
else {
$definition = $schema->getDefinitionFromDatabase();
$definition['charset'] = 'utf8';
if (PEAR::isError($definition)) {
$this->raise_error(array('code' => $definition->getCode(), 'message' => $definition->getMessage() . ' ' . $definition->getUserInfo()));
return false;
}
// load reference schema
$dsn_arr = MDB2::parseDSN($this->config['db_dsnw']);
$ref_schema = INSTALL_PATH . 'SQL/' . $dsn_arr['phptype'] . '.schema.xml';
if (is_readable($ref_schema)) {
$reference = $schema->parseDatabaseDefinition($ref_schema, false, array(), $schema->options['fail_on_invalid_names']);
if (PEAR::isError($reference)) {
$this->raise_error(array('code' => $reference->getCode(), 'message' => $reference->getMessage() . ' ' . $reference->getUserInfo()));
}
else {
$diff = $schema->compareDefinitions($reference, $definition);
if (empty($diff)) {
return true;
}
else if ($update) {
// update database schema with the diff from the above check
$success = $schema->alterDatabase($reference, $definition, $diff);
if (PEAR::isError($success)) {
$this->raise_error(array('code' => $success->getCode(), 'message' => $success->getMessage() . ' ' . $success->getUserInfo()));
}
else
return true;
}
echo '<pre>'; var_dump($diff); echo '</pre>';
return false;
}
}
else
$this->raise_error(array('message' => "Could not find reference schema file ($ref_schema)"));
return false;
}
return false;
}
/**
* Getter for the last error message
*
* @return string Error message or null if none exists
*/
function get_error()
{
return $this->last_error['message'];
}
/**
* Return a list with all imap hosts configured
*
* @return array Clean list with imap hosts
*/
function get_hostlist()
{
$default_hosts = (array)$this->getprop('default_host');
$out = array();
foreach ($default_hosts as $key => $name) {
if (!empty($name))
$out[] = rcube_parse_host(is_numeric($key) ? $name : $key);
}
return $out;
}
/**
* Create a HTML dropdown to select a previous version of Roundcube
*/
function versions_select($attrib = array())
{
$select = new html_select($attrib);
$select->add(array(
'0.1-stable', '0.1.1',
'0.2-alpha', '0.2-beta', '0.2-stable',
'0.3-stable', '0.3.1',
'0.4-beta', '0.4.2',
'0.5-beta', '0.5', '0.5.1',
'0.6-beta', '0.6',
'0.7-beta', '0.7',
));
return $select;
}
/**
* Return a list with available subfolders of the skin directory
*/
function list_skins()
{
$skins = array();
$skindir = INSTALL_PATH . 'skins/';
foreach (glob($skindir . '*') as $path) {
if (is_dir($path) && is_readable($path)) {
$skins[] = substr($path, strlen($skindir));
}
}
return $skins;
}
/**
* Display OK status
*
* @param string Test name
* @param string Confirm message
*/
function pass($name, $message = '')
{
echo Q($name) . ':&nbsp; <span class="success">OK</span>';
$this->_showhint($message);
}
/**
* Display an error status and increase failure count
*
* @param string Test name
* @param string Error message
* @param string URL for details
*/
function fail($name, $message = '', $url = '')
{
$this->failures++;
echo Q($name) . ':&nbsp; <span class="fail">NOT OK</span>';
$this->_showhint($message, $url);
}
/**
* Display an error status for optional settings/features
*
* @param string Test name
* @param string Error message
* @param string URL for details
*/
function optfail($name, $message = '', $url = '')
{
echo Q($name) . ':&nbsp; <span class="na">NOT OK</span>';
$this->_showhint($message, $url);
}
/**
* Display warning status
*
* @param string Test name
* @param string Warning message
* @param string URL for details
*/
function na($name, $message = '', $url = '')
{
echo Q($name) . ':&nbsp; <span class="na">NOT AVAILABLE</span>';
$this->_showhint($message, $url);
}
function _showhint($message, $url = '')
{
$hint = Q($message);
if ($url)
$hint .= ($hint ? '; ' : '') . 'See <a href="' . Q($url) . '" target="_blank">' . Q($url) . '</a>';
if ($hint)
echo '<span class="indent">(' . $hint . ')</span>';
}
static function _clean_array($arr)
{
$out = array();
foreach (array_unique($arr) as $k => $val) {
if (!empty($val)) {
if (is_numeric($k))
$out[] = $val;
else
$out[$k] = $val;
}
}
return $out;
}
static function _dump_var($var, $name=null) {
// special values
switch ($name) {
case 'syslog_facility':
$list = array(32 => 'LOG_AUTH', 80 => 'LOG_AUTHPRIV', 72 => ' LOG_CRON',
24 => 'LOG_DAEMON', 0 => 'LOG_KERN', 128 => 'LOG_LOCAL0',
136 => 'LOG_LOCAL1', 144 => 'LOG_LOCAL2', 152 => 'LOG_LOCAL3',
160 => 'LOG_LOCAL4', 168 => 'LOG_LOCAL5', 176 => 'LOG_LOCAL6',
184 => 'LOG_LOCAL7', 48 => 'LOG_LPR', 16 => 'LOG_MAIL',
56 => 'LOG_NEWS', 40 => 'LOG_SYSLOG', 8 => 'LOG_USER', 64 => 'LOG_UUCP');
if ($val = $list[$var])
return $val;
break;
}
if (is_array($var)) {
if (empty($var)) {
return 'array()';
}
else { // check if all keys are numeric
$isnum = true;
foreach ($var as $key => $value) {
if (!is_numeric($key)) {
$isnum = false;
break;
}
}
if ($isnum)
return 'array(' . join(', ', array_map(array('rcube_install', '_dump_var'), $var)) . ')';
}
}
return var_export($var, true);
}
/**
* Initialize the database with the according schema
*
* @param object rcube_db Database connection
* @return boolen True on success, False on error
*/
function init_db($DB)
{
$engine = isset($this->db_map[$DB->db_provider]) ? $this->db_map[$DB->db_provider] : $DB->db_provider;
// read schema file from /SQL/*
$fname = INSTALL_PATH . "SQL/$engine.initial.sql";
if ($sql = @file_get_contents($fname)) {
$this->exec_sql($sql, $DB);
}
else {
$this->fail('DB Schema', "Cannot read the schema file: $fname");
return false;
}
if ($err = $this->get_error()) {
$this->fail('DB Schema', "Error creating database schema: $err");
return false;
}
return true;
}
/**
* Update database with SQL statements from SQL/*.update.sql
*
* @param object rcube_db Database connection
* @param string Version to update from
* @return boolen True on success, False on error
*/
function update_db($DB, $version)
{
$version = strtolower($version);
$engine = isset($this->db_map[$DB->db_provider]) ? $this->db_map[$DB->db_provider] : $DB->db_provider;
// read schema file from /SQL/*
$fname = INSTALL_PATH . "SQL/$engine.update.sql";
if ($lines = @file($fname, FILE_SKIP_EMPTY_LINES)) {
$from = false; $sql = '';
foreach ($lines as $line) {
$is_comment = preg_match('/^--/', $line);
if (!$from && $is_comment && preg_match('/from version\s([0-9.]+[a-z-]*)/', $line, $m)) {
$v = strtolower($m[1]);
if ($v == $version || version_compare($version, $v, '<='))
$from = true;
}
if ($from && !$is_comment)
$sql .= $line. "\n";
}
if ($sql)
$this->exec_sql($sql, $DB);
}
else {
$this->fail('DB Schema', "Cannot read the update file: $fname");
return false;
}
if ($err = $this->get_error()) {
$this->fail('DB Schema', "Error updating database: $err");
return false;
}
return true;
}
/**
* Execute the given SQL queries on the database connection
*
* @param string SQL queries to execute
* @param object rcube_db Database connection
* @return boolen True on success, False on error
*/
function exec_sql($sql, $DB)
{
$buff = '';
foreach (explode("\n", $sql) as $line) {
if (preg_match('/^--/', $line) || trim($line) == '')
continue;
$buff .= $line . "\n";
if (preg_match('/(;|^GO)$/', trim($line))) {
$DB->query($buff);
$buff = '';
if ($DB->is_error())
break;
}
}
return !$DB->is_error();
}
/**
* Handler for Roundcube errors
*/
function raise_error($p)
{
$this->last_error = $p;
}
/**
* Generarte a ramdom string to be used as encryption key
*
* @param int Key length
* @return string The generated random string
* @static
*/
function random_key($length)
{
$alpha = 'ABCDEFGHIJKLMNOPQERSTUVXYZabcdefghijklmnopqrtsuvwxyz0123456789+*%&?!$-_=';
$out = '';
for ($i=0; $i < $length; $i++)
$out .= $alpha{rand(0, strlen($alpha)-1)};
return $out;
}
}
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 59ffaea89..deaaabf54 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1,1693 +1,1697 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcmail.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Application class providing core functions and holding |
| instances of all 'global' objects like db- and imap-connections |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
/**
* Application class of Roundcube Webmail
* implemented as singleton
*
* @package Core
*/
class rcmail
{
/**
* Main tasks.
*
* @var array
*/
static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
/**
* Singleton instace of rcmail
*
* @var rcmail
*/
static private $instance;
/**
* Stores instance of rcube_config.
*
* @var rcube_config
*/
public $config;
/**
* Stores rcube_user instance.
*
* @var rcube_user
*/
public $user;
/**
* Instace of database class.
*
* @var rcube_mdb2
*/
public $db;
/**
* Instace of Memcache class.
*
* @var rcube_mdb2
*/
public $memcache;
/**
* Instace of rcube_session class.
*
* @var rcube_session
*/
public $session;
/**
* Instance of rcube_smtp class.
*
* @var rcube_smtp
*/
public $smtp;
/**
* Instance of rcube_imap class.
*
* @var rcube_imap
*/
public $imap;
/**
* Instance of rcube_template class.
*
* @var rcube_template
*/
public $output;
/**
* Instance of rcube_plugin_api.
*
* @var rcube_plugin_api
*/
public $plugins;
/**
* Current task.
*
* @var string
*/
public $task;
/**
* Current action.
*
* @var string
*/
public $action = '';
public $comm_path = './';
private $texts;
private $address_books = array();
private $caches = array();
private $action_map = array();
private $shutdown_functions = array();
/**
* This implements the 'singleton' design pattern
*
* @return rcmail The one and only instance
*/
static function get_instance()
{
if (!self::$instance) {
self::$instance = new rcmail();
self::$instance->startup(); // init AFTER object was linked with self::$instance
}
return self::$instance;
}
/**
* Private constructor
*/
private function __construct()
{
// load configuration
$this->config = new rcube_config();
register_shutdown_function(array($this, 'shutdown'));
}
/**
* Initial startup function
* to register session, create database and imap connections
*/
private function startup()
{
// initialize syslog
if ($this->config->get('log_driver') == 'syslog') {
$syslog_id = $this->config->get('syslog_id', 'roundcube');
$syslog_facility = $this->config->get('syslog_facility', LOG_USER);
openlog($syslog_id, LOG_ODELAY, $syslog_facility);
}
// connect to database
$this->get_dbh();
// set global object for backward compatibility
$GLOBALS['DB'] = $this->db;
// start session
$this->session_init();
// create user object
$this->set_user(new rcube_user($_SESSION['user_id']));
// configure session (after user config merge!)
$this->session_configure();
// set task and action properties
$this->set_task(get_input_value('_task', RCUBE_INPUT_GPC));
$this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
// reset some session parameters when changing task
if ($this->task != 'utils') {
if ($this->session && $_SESSION['task'] != $this->task)
$this->session->remove('page');
// set current task to session
$_SESSION['task'] = $this->task;
}
// init output class
if (!empty($_REQUEST['_remote']))
$GLOBALS['OUTPUT'] = $this->json_init();
else
$GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
// create plugin API and load plugins
$this->plugins = rcube_plugin_api::get_instance();
// init plugins
$this->plugins->init();
}
/**
* Setter for application task
*
* @param string Task to set
*/
public function set_task($task)
{
$task = asciiwords($task);
if ($this->user && $this->user->ID)
$task = !$task ? 'mail' : $task;
else
$task = 'login';
$this->task = $task;
$this->comm_path = $this->url(array('task' => $this->task));
if ($this->output)
$this->output->set_env('task', $this->task);
}
/**
* Setter for system user object
*
* @param rcube_user Current user instance
*/
public function set_user($user)
{
if (is_object($user)) {
$this->user = $user;
// set global object for backward compatibility
$GLOBALS['USER'] = $this->user;
// overwrite config with user preferences
$this->config->set_user_prefs((array)$this->user->get_prefs());
}
$_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
// set localization
setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
// workaround for http://bugs.php.net/bug.php?id=18556
if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
setlocale(LC_CTYPE, 'en_US' . '.utf8');
}
/**
* Check the given string and return a valid language code
*
* @param string Language code
* @return string Valid language code
*/
private function language_prop($lang)
{
static $rcube_languages, $rcube_language_aliases;
// user HTTP_ACCEPT_LANGUAGE if no language is specified
if (empty($lang) || $lang == 'auto') {
$accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
$lang = str_replace('-', '_', $accept_langs[0]);
}
if (empty($rcube_languages)) {
@include(INSTALL_PATH . 'program/localization/index.inc');
}
// check if we have an alias for that language
if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
$lang = $rcube_language_aliases[$lang];
}
// try the first two chars
else if (!isset($rcube_languages[$lang])) {
$short = substr($lang, 0, 2);
// check if we have an alias for the short language code
if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
$lang = $rcube_language_aliases[$short];
}
// expand 'nn' to 'nn_NN'
else if (!isset($rcube_languages[$short])) {
$lang = $short.'_'.strtoupper($short);
}
}
if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
$lang = 'en_US';
}
return $lang;
}
/**
* Get the current database connection
*
* @return rcube_mdb2 Database connection object
*/
public function get_dbh()
{
if (!$this->db) {
$config_all = $this->config->all();
$this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
$this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
$this->db->set_debug((bool)$config_all['sql_debug']);
}
return $this->db;
}
/**
* Get global handle for memcache access
*
* @return object Memcache
*/
public function get_memcache()
{
if (!isset($this->memcache)) {
// no memcache support in PHP
if (!class_exists('Memcache')) {
$this->memcache = false;
return false;
}
$this->memcache = new Memcache;
$this->mc_available = 0;
// add alll configured hosts to pool
$pconnect = $this->config->get('memcache_pconnect', true);
foreach ($this->config->get('memcache_hosts', array()) as $host) {
list($host, $port) = explode(':', $host);
if (!$port) $port = 11211;
$this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
}
// test connection and failover (will result in $this->mc_available == 0 on complete failure)
$this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
if (!$this->mc_available)
$this->memcache = false;
}
return $this->memcache;
}
/**
* Callback for memcache failure
*/
public function memcache_failure($host, $port)
{
static $seen = array();
// only report once
if (!$seen["$host:$port"]++) {
$this->mc_available--;
raise_error(array('code' => 604, 'type' => 'db',
'line' => __LINE__, 'file' => __FILE__,
'message' => "Memcache failure on host $host:$port"),
true, false);
}
}
/**
* Initialize and get cache object
*
* @param string $name Cache identifier
* @param string $type Cache type ('db', 'apc' or 'memcache')
* @param int $ttl Expiration time for cache items in seconds
* @param bool $packed Enables/disables data serialization
*
* @return rcube_cache Cache object
*/
public function get_cache($name, $type='db', $ttl=0, $packed=true)
{
if (!isset($this->caches[$name])) {
$this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed);
}
return $this->caches[$name];
}
/**
* Return instance of the internal address book class
*
* @param string Address book identifier
* @param boolean True if the address book needs to be writeable
*
* @return rcube_contacts Address book object
*/
public function get_address_book($id, $writeable = false)
{
$contacts = null;
$ldap_config = (array)$this->config->get('ldap_public');
$abook_type = strtolower($this->config->get('address_book_type'));
// 'sql' is the alias for '0' used by autocomplete
if ($id == 'sql')
$id = '0';
// use existing instance
if (isset($this->address_books[$id]) && is_object($this->address_books[$id])
&& is_a($this->address_books[$id], 'rcube_addressbook')
&& (!$writeable || !$this->address_books[$id]->readonly)
) {
$contacts = $this->address_books[$id];
}
else if ($id && $ldap_config[$id]) {
$contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
}
else if ($id === '0') {
$contacts = new rcube_contacts($this->db, $this->user->ID);
}
else {
$plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
// plugin returned instance of a rcube_addressbook
if ($plugin['instance'] instanceof rcube_addressbook) {
$contacts = $plugin['instance'];
}
// get first source from the list
else if (!$id) {
$source = reset($this->get_address_sources($writeable));
if (!empty($source)) {
$contacts = $this->get_address_book($source['id']);
if ($contacts)
$id = $source['id'];
}
}
}
if (!$contacts) {
raise_error(array(
'code' => 700, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Addressbook source ($id) not found!"),
true, true);
}
// add to the 'books' array for shutdown function
$this->address_books[$id] = $contacts;
return $contacts;
}
/**
* Return address books list
*
* @param boolean True if the address book needs to be writeable
*
* @return array Address books array
*/
public function get_address_sources($writeable = false)
{
$abook_type = strtolower($this->config->get('address_book_type'));
$ldap_config = $this->config->get('ldap_public');
$autocomplete = (array) $this->config->get('autocomplete_addressbooks');
$list = array();
// We are using the DB address book
if ($abook_type != 'ldap') {
if (!isset($this->address_books['0']))
$this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID);
$list['0'] = array(
'id' => '0',
'name' => rcube_label('personaladrbook'),
'groups' => $this->address_books['0']->groups,
'readonly' => $this->address_books['0']->readonly,
'autocomplete' => in_array('sql', $autocomplete),
'undelete' => $this->address_books['0']->undelete && $this->config->get('undo_timeout'),
);
}
if ($ldap_config) {
$ldap_config = (array) $ldap_config;
foreach ($ldap_config as $id => $prop)
$list[$id] = array(
'id' => $id,
'name' => $prop['name'],
'groups' => is_array($prop['groups']),
'readonly' => !$prop['writable'],
'hidden' => $prop['hidden'],
'autocomplete' => in_array($id, $autocomplete)
);
}
$plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
$list = $plugin['sources'];
foreach ($list as $idx => $item) {
// register source for shutdown function
if (!is_object($this->address_books[$item['id']]))
$this->address_books[$item['id']] = $item;
// remove from list if not writeable as requested
if ($writeable && $item['readonly'])
unset($list[$idx]);
}
return $list;
}
/**
* Init output object for GUI and add common scripts.
* This will instantiate a rcmail_template object and set
* environment vars according to the current session and configuration
*
* @param boolean True if this request is loaded in a (i)frame
* @return rcube_template Reference to HTML output object
*/
public function load_gui($framed = false)
{
// init output page
if (!($this->output instanceof rcube_template))
$this->output = new rcube_template($this->task, $framed);
// set keep-alive/check-recent interval
if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
$this->output->set_env('keep_alive', $keep_alive);
}
if ($framed) {
$this->comm_path .= '&_framed=1';
$this->output->set_env('framed', true);
}
$this->output->set_env('task', $this->task);
$this->output->set_env('action', $this->action);
$this->output->set_env('comm_path', $this->comm_path);
$this->output->set_charset(RCMAIL_CHARSET);
// add some basic labels to client
$this->output->add_label('loading', 'servererror');
return $this->output;
}
/**
* Create an output object for JSON responses
*
* @return rcube_json_output Reference to JSON output object
*/
public function json_init()
{
if (!($this->output instanceof rcube_json_output))
$this->output = new rcube_json_output($this->task);
return $this->output;
}
/**
* Create SMTP object and connect to server
*
* @param boolean True if connection should be established
*/
public function smtp_init($connect = false)
{
$this->smtp = new rcube_smtp();
if ($connect)
$this->smtp->connect();
}
/**
* Create global IMAP object and connect to server
*
* @param boolean True if connection should be established
*/
public function imap_init($connect = false)
{
// already initialized
if (is_object($this->imap))
return;
$this->imap = new rcube_imap();
$this->imap->skip_deleted = $this->config->get('skip_deleted');
// enable caching of imap data
- $imap_cache = $this->config->get('imap_cache');
+ $imap_cache = $this->config->get('imap_cache');
$messages_cache = $this->config->get('messages_cache');
// for backward compatybility
if ($imap_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
$imap_cache = 'db';
$messages_cache = true;
}
+
if ($imap_cache)
$this->imap->set_caching($imap_cache);
if ($messages_cache)
$this->imap->set_messages_caching(true);
// set pagesize from config
- $this->imap->set_pagesize($this->config->get('pagesize', 50));
+ $pagesize = $this->config->get('mail_pagesize');
+ if (!$pagesize) {
+ $pagesize = $this->config->get('pagesize', 50);
+ }
+ $this->imap->set_pagesize($pagesize);
- // Setting root and delimiter before establishing the connection
- // can save time detecting them using NAMESPACE and LIST
+ // set connection options
$options = array(
'auth_type' => $this->config->get('imap_auth_type', 'check'),
'auth_cid' => $this->config->get('imap_auth_cid'),
'auth_pw' => $this->config->get('imap_auth_pw'),
'debug' => (bool) $this->config->get('imap_debug', 0),
'force_caps' => (bool) $this->config->get('imap_force_caps'),
'timeout' => (int) $this->config->get('imap_timeout', 0),
);
$this->imap->set_options($options);
// set global object for backward compatibility
$GLOBALS['IMAP'] = $this->imap;
$hook = $this->plugins->exec_hook('imap_init', array('fetch_headers' => $this->imap->fetch_add_headers));
if ($hook['fetch_headers'])
$this->imap->fetch_add_headers = $hook['fetch_headers'];
// support this parameter for backward compatibility but log warning
if ($connect) {
$this->imap_connect();
raise_error(array(
'code' => 800, 'type' => 'imap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "rcube::imap_init(true) is deprecated, use rcube::imap_connect() instead"),
true, false);
}
}
/**
* Connect to IMAP server with stored session data
*
* @return bool True on success, false on error
*/
public function imap_connect()
{
if (!$this->imap)
$this->imap_init();
if ($_SESSION['imap_host'] && !$this->imap->conn->connected()) {
if (!$this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])) {
if ($this->output)
$this->output->show_message($this->imap->get_error_code() == -1 ? 'imaperror' : 'sessionerror', 'error');
}
else {
$this->set_imap_prop();
return $this->imap->conn;
}
}
return false;
}
/**
* Create session object and start the session.
*/
public function session_init()
{
// session started (Installer?)
if (session_id())
return;
$sess_name = $this->config->get('session_name');
$sess_domain = $this->config->get('session_domain');
$lifetime = $this->config->get('session_lifetime', 0) * 60;
// set session domain
if ($sess_domain) {
ini_set('session.cookie_domain', $sess_domain);
}
// set session garbage collecting time according to session_lifetime
if ($lifetime) {
ini_set('session.gc_maxlifetime', $lifetime * 2);
}
ini_set('session.cookie_secure', rcube_https_check());
ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
ini_set('session.use_cookies', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.serialize_handler', 'php');
// use database for storing session data
$this->session = new rcube_session($this->get_dbh(), $this->config);
$this->session->register_gc_handler('rcmail_temp_gc');
if ($this->config->get('enable_caching'))
$this->session->register_gc_handler('rcmail_cache_gc');
// start PHP session (if not in CLI mode)
if ($_SERVER['REMOTE_ADDR'])
session_start();
// set initial session vars
if (!$_SESSION['user_id'])
$_SESSION['temp'] = true;
}
/**
* Configure session object internals
*/
public function session_configure()
{
if (!$this->session)
return;
$lifetime = $this->config->get('session_lifetime', 0) * 60;
// set keep-alive/check-recent interval
if ($keep_alive = $this->config->get('keep_alive')) {
// be sure that it's less than session lifetime
if ($lifetime)
$keep_alive = min($keep_alive, $lifetime - 30);
$keep_alive = max(60, $keep_alive);
$this->session->set_keep_alive($keep_alive);
}
$this->session->set_secret($this->config->get('des_key') . $_SERVER['HTTP_USER_AGENT']);
$this->session->set_ip_check($this->config->get('ip_check'));
}
/**
* Perfom login to the IMAP server and to the webmail service.
* This will also create a new user entry if auto_create_user is configured.
*
* @param string IMAP user name
* @param string IMAP password
* @param string IMAP host
* @return boolean True on success, False on failure
*/
function login($username, $pass, $host=NULL)
{
$user = NULL;
$config = $this->config->all();
if (!$host)
$host = $config['default_host'];
// Validate that selected host is in the list of configured hosts
if (is_array($config['default_host'])) {
$allowed = false;
foreach ($config['default_host'] as $key => $host_allowed) {
if (!is_numeric($key))
$host_allowed = $key;
if ($host == $host_allowed) {
$allowed = true;
break;
}
}
if (!$allowed)
return false;
}
else if (!empty($config['default_host']) && $host != rcube_parse_host($config['default_host']))
return false;
// parse $host URL
$a_host = parse_url($host);
if ($a_host['host']) {
$host = $a_host['host'];
$imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
if (!empty($a_host['port']))
$imap_port = $a_host['port'];
else if ($imap_ssl && $imap_ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
$imap_port = 993;
}
$imap_port = $imap_port ? $imap_port : $config['default_port'];
/* Modify username with domain if required
Inspired by Marco <P0L0_notspam_binware.org>
*/
// Check if we need to add domain
if (!empty($config['username_domain']) && strpos($username, '@') === false) {
if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
$username .= '@'.rcube_parse_host($config['username_domain'][$host], $host);
else if (is_string($config['username_domain']))
$username .= '@'.rcube_parse_host($config['username_domain'], $host);
}
// Convert username to lowercase. If IMAP backend
// is case-insensitive we need to store always the same username (#1487113)
if ($config['login_lc']) {
$username = mb_strtolower($username);
}
// try to resolve email address from virtuser table
if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
$username = $virtuser;
}
// Here we need IDNA ASCII
// Only rcube_contacts class is using domain names in Unicode
$host = rcube_idn_to_ascii($host);
if (strpos($username, '@')) {
// lowercase domain name
list($local, $domain) = explode('@', $username);
$username = $local . '@' . mb_strtolower($domain);
$username = rcube_idn_to_ascii($username);
}
// user already registered -> overwrite username
if ($user = rcube_user::query($username, $host))
$username = $user->data['username'];
if (!$this->imap)
$this->imap_init();
// try IMAP login
if (!($imap_login = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl))) {
// try with lowercase
$username_lc = mb_strtolower($username);
if ($username_lc != $username) {
// try to find user record again -> overwrite username
if (!$user && ($user = rcube_user::query($username_lc, $host)))
$username_lc = $user->data['username'];
if ($imap_login = $this->imap->connect($host, $username_lc, $pass, $imap_port, $imap_ssl))
$username = $username_lc;
}
}
// exit if IMAP login failed
if (!$imap_login)
return false;
// user already registered -> update user's record
if (is_object($user)) {
// update last login timestamp
$user->touch();
}
// create new system user
else if ($config['auto_create_user']) {
if ($created = rcube_user::create($username, $host)) {
$user = $created;
}
else {
raise_error(array(
'code' => 620, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to create a user record. Maybe aborted by a plugin?"
), true, false);
}
}
else {
raise_error(array(
'code' => 621, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
), true, false);
}
// login succeeded
if (is_object($user) && $user->ID) {
// Configure environment
$this->set_user($user);
$this->set_imap_prop();
$this->session_configure();
// fix some old settings according to namespace prefix
$this->fix_namespace_settings($user);
// create default folders on first login
if ($config['create_default_folders'] && (!empty($created) || empty($user->data['last_login']))) {
$this->imap->create_default_folders();
}
// set session vars
$_SESSION['user_id'] = $user->ID;
$_SESSION['username'] = $user->data['username'];
$_SESSION['imap_host'] = $host;
$_SESSION['imap_port'] = $imap_port;
$_SESSION['imap_ssl'] = $imap_ssl;
$_SESSION['password'] = $this->encrypt($pass);
$_SESSION['login_time'] = mktime();
if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
$_SESSION['timezone'] = floatval($_REQUEST['_timezone']);
if (isset($_REQUEST['_dstactive']) && $_REQUEST['_dstactive'] != '_default_')
$_SESSION['dst_active'] = intval($_REQUEST['_dstactive']);
// force reloading complete list of subscribed mailboxes
$this->imap->clear_cache('mailboxes', true);
return true;
}
return false;
}
/**
* Set root dir and last stored mailbox
* This must be done AFTER connecting to the server!
*/
public function set_imap_prop()
{
$this->imap->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
if ($default_folders = $this->config->get('default_imap_folders')) {
$this->imap->set_default_mailboxes($default_folders);
}
if (isset($_SESSION['mbox'])) {
$this->imap->set_mailbox($_SESSION['mbox']);
}
if (isset($_SESSION['page'])) {
$this->imap->set_page($_SESSION['page']);
}
}
/**
* Auto-select IMAP host based on the posted login information
*
* @return string Selected IMAP host
*/
public function autoselect_host()
{
$default_host = $this->config->get('default_host');
$host = null;
if (is_array($default_host)) {
$post_host = get_input_value('_host', RCUBE_INPUT_POST);
// direct match in default_host array
if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
$host = $post_host;
}
// try to select host by mail domain
list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST));
if (!empty($domain)) {
foreach ($default_host as $imap_host => $mail_domains) {
if (is_array($mail_domains) && in_array($domain, $mail_domains)) {
$host = $imap_host;
break;
}
}
}
// take the first entry if $host is still an array
if (empty($host)) {
$host = array_shift($default_host);
}
}
else if (empty($default_host)) {
$host = get_input_value('_host', RCUBE_INPUT_POST);
}
else
$host = rcube_parse_host($default_host);
return $host;
}
/**
* Get localized text in the desired language
*
* @param mixed $attrib Named parameters array or label name
* @param string $domain Label domain (plugin) name
*
* @return string Localized text
*/
public function gettext($attrib, $domain=null)
{
// load localization files if not done yet
if (empty($this->texts))
$this->load_language();
// extract attributes
if (is_string($attrib))
$attrib = array('name' => $attrib);
$name = $attrib['name'] ? $attrib['name'] : '';
// attrib contain text values: use them from now
if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
$this->texts[$name] = $setval;
// check for text with domain
if ($domain && ($text = $this->texts[$domain.'.'.$name]))
;
// text does not exist
else if (!($text = $this->texts[$name])) {
return "[$name]";
}
// replace vars in text
if (is_array($attrib['vars'])) {
foreach ($attrib['vars'] as $var_key => $var_value)
$text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
}
// format output
if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
return ucfirst($text);
else if ($attrib['uppercase'])
return mb_strtoupper($text);
else if ($attrib['lowercase'])
return mb_strtolower($text);
return strtr($text, array('\n' => "\n"));
}
/**
* Check if the given text label exists
*
* @param string $name Label name
* @param string $domain Label domain (plugin) name or '*' for all domains
* @param string $ref_domain Sets domain name if label is found
*
* @return boolean True if text exists (either in the current language or in en_US)
*/
public function text_exists($name, $domain = null, &$ref_domain = null)
{
// load localization files if not done yet
if (empty($this->texts))
$this->load_language();
if (isset($this->texts[$name])) {
$ref_domain = '';
return true;
}
// any of loaded domains (plugins)
if ($domain == '*') {
foreach ($this->plugins->loaded_plugins() as $domain)
if (isset($this->texts[$domain.'.'.$name])) {
$ref_domain = $domain;
return true;
}
}
// specified domain
else if ($domain) {
$ref_domain = $domain;
return isset($this->texts[$domain.'.'.$name]);
}
return false;
}
/**
* Load a localization package
*
* @param string Language ID
*/
public function load_language($lang = null, $add = array())
{
$lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
// load localized texts
if (empty($this->texts) || $lang != $_SESSION['language']) {
$this->texts = array();
// handle empty lines after closing PHP tag in localization files
ob_start();
// get english labels (these should be complete)
@include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
@include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
if (is_array($labels))
$this->texts = $labels;
if (is_array($messages))
$this->texts = array_merge($this->texts, $messages);
// include user language files
if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
if (is_array($labels))
$this->texts = array_merge($this->texts, $labels);
if (is_array($messages))
$this->texts = array_merge($this->texts, $messages);
}
ob_end_clean();
$_SESSION['language'] = $lang;
}
// append additional texts (from plugin)
if (is_array($add) && !empty($add))
$this->texts += $add;
}
/**
* Read directory program/localization and return a list of available languages
*
* @return array List of available localizations
*/
public function list_languages()
{
static $sa_languages = array();
if (!sizeof($sa_languages)) {
@include(INSTALL_PATH . 'program/localization/index.inc');
if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
while (($name = readdir($dh)) !== false) {
if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
continue;
if ($label = $rcube_languages[$name])
$sa_languages[$name] = $label;
}
closedir($dh);
}
}
return $sa_languages;
}
/**
* Destroy session data and remove cookie
*/
public function kill_session()
{
$this->plugins->exec_hook('session_destroy');
$this->session->kill();
$_SESSION = array('language' => $this->user->language, 'temp' => true);
$this->user->reset();
}
/**
* Do server side actions on logout
*/
public function logout_actions()
{
$config = $this->config->all();
// on logout action we're not connected to imap server
if (($config['logout_purge'] && !empty($config['trash_mbox'])) || $config['logout_expunge']) {
if (!$this->session->check_auth())
return;
$this->imap_connect();
}
if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
$this->imap->clear_mailbox($config['trash_mbox']);
}
if ($config['logout_expunge']) {
$this->imap->expunge('INBOX');
}
// Try to save unsaved user preferences
if (!empty($_SESSION['preferences'])) {
$this->user->save_prefs(unserialize($_SESSION['preferences']));
}
}
/**
* Function to be executed in script shutdown
* Registered with register_shutdown_function()
*/
public function shutdown()
{
foreach ($this->shutdown_functions as $function)
call_user_func($function);
if (is_object($this->smtp))
$this->smtp->disconnect();
foreach ($this->address_books as $book) {
if (is_object($book) && is_a($book, 'rcube_addressbook'))
$book->close();
}
foreach ($this->caches as $cache) {
if (is_object($cache))
$cache->close();
}
if (is_object($this->imap))
$this->imap->close();
// before closing the database connection, write session data
if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
session_write_close();
}
// write performance stats to logs/console
if ($this->config->get('devel_mode')) {
if (function_exists('memory_get_usage'))
$mem = show_bytes(memory_get_usage());
if (function_exists('memory_get_peak_usage'))
$mem .= '/'.show_bytes(memory_get_peak_usage());
$log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
if (defined('RCMAIL_START'))
rcube_print_time(RCMAIL_START, $log);
else
console($log);
}
}
/**
* Registers shutdown function to be executed on shutdown.
* The functions will be executed before destroying any
* objects like smtp, imap, session, etc.
*
* @param callback Function callback
*/
public function add_shutdown_function($function)
{
$this->shutdown_functions[] = $function;
}
/**
* Generate a unique token to be used in a form request
*
* @return string The request token
*/
public function get_request_token()
{
$sess_id = $_COOKIE[ini_get('session.name')];
if (!$sess_id) $sess_id = session_id();
$plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->user->ID . $this->config->get('des_key') . $sess_id)));
return $plugin['value'];
}
/**
* Check if the current request contains a valid token
*
* @param int Request method
* @return boolean True if request token is valid false if not
*/
public function check_request($mode = RCUBE_INPUT_POST)
{
$token = get_input_value('_token', $mode);
$sess_id = $_COOKIE[ini_get('session.name')];
return !empty($sess_id) && $token == $this->get_request_token();
}
/**
* Create unique authorization hash
*
* @param string Session ID
* @param int Timestamp
* @return string The generated auth hash
*/
private function get_auth_hash($sess_id, $ts)
{
$auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
$sess_id,
$ts,
$this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
$_SERVER['HTTP_USER_AGENT']);
if (function_exists('sha1'))
return sha1($auth_string);
else
return md5($auth_string);
}
/**
* Encrypt using 3DES
*
* @param string $clear clear text input
* @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
* @param boolean $base64 whether or not to base64_encode() the result before returning
*
* @return string encrypted text
*/
public function encrypt($clear, $key = 'des_key', $base64 = true)
{
if (!$clear)
return '';
/*-
* Add a single canary byte to the end of the clear text, which
* will help find out how much of padding will need to be removed
* upon decryption; see http://php.net/mcrypt_generic#68082
*/
$clear = pack("a*H2", $clear, "80");
if (function_exists('mcrypt_module_open') &&
($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
{
$iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
$cipher = $iv . mcrypt_generic($td, $clear);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
}
else {
@include_once 'des.inc';
if (function_exists('des')) {
$des_iv_size = 8;
$iv = $this->create_iv($des_iv_size);
$cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
}
else {
raise_error(array(
'code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
), true, true);
}
}
return $base64 ? base64_encode($cipher) : $cipher;
}
/**
* Decrypt 3DES-encrypted string
*
* @param string $cipher encrypted text
* @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
* @param boolean $base64 whether or not input is base64-encoded
*
* @return string decrypted text
*/
public function decrypt($cipher, $key = 'des_key', $base64 = true)
{
if (!$cipher)
return '';
$cipher = $base64 ? base64_decode($cipher) : $cipher;
if (function_exists('mcrypt_module_open') &&
($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
{
$iv_size = mcrypt_enc_get_iv_size($td);
$iv = substr($cipher, 0, $iv_size);
// session corruption? (#1485970)
if (strlen($iv) < $iv_size)
return '';
$cipher = substr($cipher, $iv_size);
mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
$clear = mdecrypt_generic($td, $cipher);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
}
else {
@include_once 'des.inc';
if (function_exists('des')) {
$des_iv_size = 8;
$iv = substr($cipher, 0, $des_iv_size);
$cipher = substr($cipher, $des_iv_size);
$clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
}
else {
raise_error(array(
'code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
), true, true);
}
}
/*-
* Trim PHP's padding and the canary byte; see note in
* rcmail::encrypt() and http://php.net/mcrypt_generic#68082
*/
$clear = substr(rtrim($clear, "\0"), 0, -1);
return $clear;
}
/**
* Generates encryption initialization vector (IV)
*
* @param int Vector size
* @return string Vector string
*/
private function create_iv($size)
{
// mcrypt_create_iv() can be slow when system lacks entrophy
// we'll generate IV vector manually
$iv = '';
for ($i = 0; $i < $size; $i++)
$iv .= chr(mt_rand(0, 255));
return $iv;
}
/**
* Build a valid URL to this instance of Roundcube
*
* @param mixed Either a string with the action or url parameters as key-value pairs
* @return string Valid application URL
*/
public function url($p)
{
if (!is_array($p))
$p = array('_action' => @func_get_arg(0));
$task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
$p['_task'] = $task;
unset($p['task']);
$url = './';
$delm = '?';
foreach (array_reverse($p) as $key => $val) {
if ($val !== '') {
$par = $key[0] == '_' ? $key : '_'.$key;
$url .= $delm.urlencode($par).'='.urlencode($val);
$delm = '&';
}
}
return $url;
}
/**
* Use imagemagick or GD lib to read image properties
*
* @param string Absolute file path
* @return mixed Hash array with image props like type, width, height or False on error
*/
public static function imageprops($filepath)
{
$rcmail = rcmail::get_instance();
if ($cmd = $rcmail->config->get('im_identify_path', false)) {
list(, $type, $size) = explode(' ', strtolower(rcmail::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath))));
if ($size)
list($width, $height) = explode('x', $size);
}
else if (function_exists('getimagesize')) {
$imsize = @getimagesize($filepath);
$width = $imsize[0];
$height = $imsize[1];
$type = preg_replace('!image/!', '', $imsize['mime']);
}
return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false;
}
/**
* Convert an image to a given size and type using imagemagick (ensures input is an image)
*
* @param $p['in'] Input filename (mandatory)
* @param $p['out'] Output filename (mandatory)
* @param $p['size'] Width x height of resulting image, e.g. "160x60"
* @param $p['type'] Output file type, e.g. "jpg"
* @param $p['-opts'] Custom command line options to ImageMagick convert
* @return Success of convert as true/false
*/
public static function imageconvert($p)
{
$result = false;
$rcmail = rcmail::get_instance();
$convert = $rcmail->config->get('im_convert_path', false);
$identify = $rcmail->config->get('im_identify_path', false);
// imagemagick is required for this
if (!$convert)
return false;
if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false))))
list(, $type) = explode(' ', strtolower(rcmail::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps
$type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
$p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
$p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts'];
if (in_array($type, explode(',', $p['types']))) # Valid type?
$result = rcmail::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === "";
return $result;
}
/**
* Construct shell command, execute it and return output as string.
* Keywords {keyword} are replaced with arguments
*
* @param $cmd Format string with {keywords} to be replaced
* @param $values (zero, one or more arrays can be passed)
* @return output of command. shell errors not detectable
*/
public static function exec(/* $cmd, $values1 = array(), ... */)
{
$args = func_get_args();
$cmd = array_shift($args);
$values = $replacements = array();
// merge values into one array
foreach ($args as $arg)
$values += (array)$arg;
preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
foreach ($matches as $tags) {
list(, $tag, $option, $key) = $tags;
$parts = array();
if ($option) {
foreach ((array)$values["-$key"] as $key => $value) {
if ($value === true || $value === false || $value === null)
$parts[] = $value ? $key : "";
else foreach ((array)$value as $val)
$parts[] = "$key " . escapeshellarg($val);
}
}
else {
foreach ((array)$values[$key] as $value)
$parts[] = escapeshellarg($value);
}
$replacements[$tag] = join(" ", $parts);
}
// use strtr behaviour of going through source string once
$cmd = strtr($cmd, $replacements);
return (string)shell_exec($cmd);
}
/**
* Helper method to set a cookie with the current path and host settings
*
* @param string Cookie name
* @param string Cookie value
* @param string Expiration time
*/
public static function setcookie($name, $value, $exp = 0)
{
if (headers_sent())
return;
$cookie = session_get_cookie_params();
setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
rcube_https_check(), true);
}
/**
* Registers action aliases for current task
*
* @param array $map Alias-to-filename hash array
*/
public function register_action_map($map)
{
if (is_array($map)) {
foreach ($map as $idx => $val) {
$this->action_map[$idx] = $val;
}
}
}
/**
* Returns current action filename
*
* @param array $map Alias-to-filename hash array
*/
public function get_action_file()
{
if (!empty($this->action_map[$this->action])) {
return $this->action_map[$this->action];
}
return strtr($this->action, '-', '_') . '.inc';
}
/**
* Fixes some user preferences according to namespace handling change.
* Old Roundcube versions were using folder names with removed namespace prefix.
* Now we need to add the prefix on servers where personal namespace has prefix.
*
* @param rcube_user $user User object
*/
private function fix_namespace_settings($user)
{
$prefix = $this->imap->get_namespace('prefix');
$prefix_len = strlen($prefix);
if (!$prefix_len)
return;
$prefs = $this->config->all();
if (!empty($prefs['namespace_fixed']))
return;
// Build namespace prefix regexp
$ns = $this->imap->get_namespace();
$regexp = array();
foreach ($ns as $entry) {
if (!empty($entry)) {
foreach ($entry as $item) {
if (strlen($item[0])) {
$regexp[] = preg_quote($item[0], '/');
}
}
}
}
$regexp = '/^('. implode('|', $regexp).')/';
// Fix preferences
$opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox');
foreach ($opts as $opt) {
if ($value = $prefs[$opt]) {
if ($value != 'INBOX' && !preg_match($regexp, $value)) {
$prefs[$opt] = $prefix.$value;
}
}
}
if (!empty($prefs['default_imap_folders'])) {
foreach ($prefs['default_imap_folders'] as $idx => $name) {
if ($name != 'INBOX' && !preg_match($regexp, $name)) {
$prefs['default_imap_folders'][$idx] = $prefix.$name;
}
}
}
if (!empty($prefs['search_mods'])) {
$folders = array();
foreach ($prefs['search_mods'] as $idx => $value) {
if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) {
$idx = $prefix.$idx;
}
$folders[$idx] = $value;
}
$prefs['search_mods'] = $folders;
}
if (!empty($prefs['message_threading'])) {
$folders = array();
foreach ($prefs['message_threading'] as $idx => $value) {
if ($idx != 'INBOX' && !preg_match($regexp, $idx)) {
$idx = $prefix.$idx;
}
$folders[$prefix.$idx] = $value;
}
$prefs['message_threading'] = $folders;
}
if (!empty($prefs['collapsed_folders'])) {
$folders = explode('&&', $prefs['collapsed_folders']);
$count = count($folders);
$folders_str = '';
if ($count) {
$folders[0] = substr($folders[0], 1);
$folders[$count-1] = substr($folders[$count-1], 0, -1);
}
foreach ($folders as $value) {
if ($value != 'INBOX' && !preg_match($regexp, $value)) {
$value = $prefix.$value;
}
$folders_str .= '&'.$value.'&';
}
$prefs['collapsed_folders'] = $folders_str;
}
$prefs['namespace_fixed'] = true;
// save updated preferences and reset imap settings (default folders)
$user->save_prefs($prefs);
$this->set_imap_prop();
}
}
diff --git a/program/js/app.js b/program/js/app.js
index e9301f568..5e51cbbcb 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -1,6272 +1,6262 @@
/*
+-----------------------------------------------------------------------+
| Roundcube Webmail Client Script |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
| Authors: Thomas Bruederli <roundcube@gmail.com> |
| Aleksander 'A.L.E.C' Machniak <alec@alec.pl> |
| Charles McNulty <charles@charlesmcnulty.com> |
+-----------------------------------------------------------------------+
| Requires: jquery.js, common.js, list.js |
+-----------------------------------------------------------------------+
$Id$
*/
function rcube_webmail()
{
this.env = { recipients_separator:',', recipients_delimiter:', ' };
this.labels = {};
this.buttons = {};
this.buttons_sel = {};
this.gui_objects = {};
this.gui_containers = {};
this.commands = {};
this.command_handlers = {};
this.onloads = [];
this.messages = {};
// create protected reference to myself
this.ref = 'rcmail';
var ref = this;
// webmail client settings
this.dblclick_time = 500;
this.message_time = 4000;
this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi');
// default environment vars
this.env.keep_alive = 60; // seconds
this.env.request_timeout = 180; // seconds
this.env.draft_autosave = 0; // seconds
this.env.comm_path = './';
this.env.blankpage = 'program/blank.gif';
// set jQuery ajax options
$.ajaxSetup({
cache:false,
error:function(request, status, err){ ref.http_error(request, status, err); },
beforeSend:function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
});
// set environment variable(s)
this.set_env = function(p, value)
{
if (p != null && typeof p === 'object' && !value)
for (var n in p)
this.env[n] = p[n];
else
this.env[p] = value;
};
// add a localized label to the client environment
this.add_label = function(p, value)
{
if (typeof p == 'string')
this.labels[p] = value;
else if (typeof p == 'object')
$.extend(this.labels, p);
};
// add a button to the button list
this.register_button = function(command, id, type, act, sel, over)
{
if (!this.buttons[command])
this.buttons[command] = [];
var button_prop = {id:id, type:type};
if (act) button_prop.act = act;
if (sel) button_prop.sel = sel;
if (over) button_prop.over = over;
this.buttons[command].push(button_prop);
if (this.loaded)
init_button(command, button_prop);
};
// register a specific gui object
this.gui_object = function(name, id)
{
this.gui_objects[name] = this.loaded ? rcube_find_object(id) : id;
};
// register a container object
this.gui_container = function(name, id)
{
this.gui_containers[name] = id;
};
// add a GUI element (html node) to a specified container
this.add_element = function(elm, container)
{
if (this.gui_containers[container] && this.gui_containers[container].jquery)
this.gui_containers[container].append(elm);
};
// register an external handler for a certain command
this.register_command = function(command, callback, enable)
{
this.command_handlers[command] = callback;
if (enable)
this.enable_command(command, true);
};
// execute the given script on load
this.add_onload = function(f)
{
this.onloads.push(f);
};
// initialize webmail client
this.init = function()
{
var n, p = this;
this.task = this.env.task;
// check browser
if (!bw.dom || !bw.xmlhttp_test()) {
this.goto_url('error', '_code=0x199');
return;
}
// find all registered gui containers
for (n in this.gui_containers)
this.gui_containers[n] = $('#'+this.gui_containers[n]);
// find all registered gui objects
for (n in this.gui_objects)
this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
// clickjacking protection
if (this.env.x_frame_options) {
try {
// bust frame if not allowed
if (this.env.x_frame_options == 'deny' && top.location.href != self.location.href)
top.location.href = self.location.href;
else if (top.location.hostname != self.location.hostname)
throw 1;
} catch (e) {
// possible clickjacking attack: disable all form elements
$('form').each(function(){ ref.lock_form(this, true); });
this.display_message("Blocked: possible clickjacking attack!", 'error');
return;
}
}
// init registered buttons
this.init_buttons();
// tell parent window that this frame is loaded
if (this.is_framed()) {
parent.rcmail.set_busy(false, null, parent.rcmail.env.frame_lock);
parent.rcmail.env.frame_lock = null;
}
// enable general commands
this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', 'about', true);
if (this.env.permaurl)
this.enable_command('permaurl', true);
switch (this.task) {
case 'mail':
// enable mail commands
this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
if (this.gui_objects.messagelist) {
this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {
multiselect:true, multiexpand:true, draggable:true, keyboard:true,
column_movable:this.env.col_movable, dblclick_time:this.dblclick_time
});
this.message_list.row_init = function(o){ p.init_message_row(o); };
this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); });
this.message_list.addEventListener('click', function(o){ p.msglist_click(o); });
this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
this.message_list.addEventListener('select', function(o){ p.msglist_select(o); });
this.message_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
this.message_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); });
this.message_list.addEventListener('expandcollapse', function(e){ p.msglist_expand(e); });
this.message_list.addEventListener('column_replace', function(e){ p.msglist_set_coltypes(e); });
document.onmouseup = function(e){ return p.doc_mouse_up(e); };
this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
this.message_list.init();
this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', true);
// load messages
this.command('list');
}
if (this.gui_objects.qsearchbox) {
if (this.env.search_text != null) {
this.gui_objects.qsearchbox.value = this.env.search_text;
}
$(this.gui_objects.qsearchbox).focusin(function() { rcmail.message_list.blur(); });
}
if (!this.env.flag_for_deletion && this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox)
this.set_alttext('delete', 'movemessagetotrash');
this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list', 'forward',
'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', 'download',
'print', 'load-attachment', 'load-headers', 'forward-attachment'];
if (this.env.action == 'show' || this.env.action == 'preview') {
this.enable_command(this.env.message_commands, this.env.uid);
this.enable_command('reply-list', this.env.list_post);
if (this.env.action == 'show') {
this.http_request('pagenav', '_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox)
+ (this.env.search_request ? '&_search='+this.env.search_request : ''),
this.display_message('', 'loading'));
}
if (this.env.blockedobjects) {
if (this.gui_objects.remoteobjectsmsg)
this.gui_objects.remoteobjectsmsg.style.display = 'block';
this.enable_command('load-images', 'always-load', true);
}
// make preview/message frame visible
if (this.env.action == 'preview' && this.is_framed()) {
this.enable_command('compose', 'add-contact', false);
parent.rcmail.show_contentframe(true);
}
}
else if (this.env.action == 'compose') {
this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor'];
if (this.env.drafts_mailbox)
this.env.compose_commands.push('savedraft')
this.enable_command(this.env.compose_commands, 'identities', true);
if (this.env.spellcheck) {
this.env.spellcheck.spelling_state_observer = function(s){ ref.set_spellcheck_state(s); };
this.env.compose_commands.push('spellcheck')
this.set_spellcheck_state('ready');
if ($("input[name='_is_html']").val() == '1')
this.display_spellcheck_controls(false);
}
document.onmouseup = function(e){ return p.doc_mouse_up(e); };
// init message compose form
this.init_messageform();
}
// show printing dialog
else if (this.env.action == 'print' && this.env.uid)
if (bw.safari)
window.setTimeout('window.print()', 10);
else
window.print();
// get unread count for each mailbox
if (this.gui_objects.mailboxlist) {
this.env.unread_counts = {};
this.gui_objects.folderlist = this.gui_objects.mailboxlist;
this.http_request('getunread', '');
}
// ask user to send MDN
if (this.env.mdn_request && this.env.uid) {
var mdnurl = '_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox);
if (confirm(this.get_label('mdnrequest')))
this.http_post('sendmdn', mdnurl);
else
this.http_post('mark', mdnurl+'&_flag=mdnsent');
}
break;
case 'addressbook':
if (this.gui_objects.folderlist)
this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups);
this.enable_command('add', 'import', this.env.writable_source);
this.enable_command('list', 'listgroup', 'listsearch', 'advanced-search', true);
if (this.gui_objects.contactslist) {
this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
{multiselect:true, draggable:this.gui_objects.folderlist?true:false, keyboard:true});
this.contact_list.row_init = function(row){ p.triggerEvent('insertrow', { cid:row.uid, row:row }); };
this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); });
this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); });
this.contact_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
this.contact_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
this.contact_list.addEventListener('dragend', function(e){ p.drag_end(e); });
this.contact_list.init();
if (this.env.cid)
this.contact_list.highlight_row(this.env.cid);
this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
document.onmouseup = function(e){ return p.doc_mouse_up(e); };
if (this.gui_objects.qsearchbox) {
$(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); });
}
this.update_group_commands();
this.command('list');
}
this.set_page_buttons();
if (this.env.cid) {
this.enable_command('show', 'edit', true);
// register handlers for group assignment via checkboxes
if (this.gui_objects.editform) {
$('input.groupmember').change(function() {
ref.group_member_change(this.checked ? 'add' : 'del', ref.env.cid, ref.env.source, this.value);
});
}
}
if (this.gui_objects.editform) {
this.enable_command('save', true);
if (this.env.action == 'add' || this.env.action == 'edit')
this.init_contact_form();
}
if (this.gui_objects.qsearchbox) {
this.enable_command('search', 'reset-search', 'moveto', true);
}
break;
case 'settings':
this.enable_command('preferences', 'identities', 'save', 'folders', true);
if (this.env.action == 'identities') {
this.enable_command('add', this.env.identities_level < 2);
}
else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') {
this.enable_command('add', this.env.identities_level < 2);
this.enable_command('save', 'delete', 'edit', 'toggle-editor', true);
}
else if (this.env.action == 'folders') {
this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true);
}
else if (this.env.action == 'edit-folder' && this.gui_objects.editform) {
this.enable_command('save', 'folder-size', true);
parent.rcmail.env.messagecount = this.env.messagecount;
parent.rcmail.enable_command('purge', this.env.messagecount);
$("input[type='text']").first().select();
}
if (this.gui_objects.identitieslist) {
this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, {multiselect:false, draggable:false, keyboard:false});
this.identity_list.addEventListener('select', function(o){ p.identity_select(o); });
this.identity_list.init();
this.identity_list.focus();
if (this.env.iid)
this.identity_list.highlight_row(this.env.iid);
}
else if (this.gui_objects.sectionslist) {
this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:false});
this.sections_list.addEventListener('select', function(o){ p.section_select(o); });
this.sections_list.init();
this.sections_list.focus();
}
else if (this.gui_objects.subscriptionlist)
this.init_subscription_list();
break;
case 'login':
var input_user = $('#rcmloginuser');
input_user.bind('keyup', function(e){ return rcmail.login_user_keyup(e); });
if (input_user.val() == '')
input_user.focus();
else
$('#rcmloginpwd').focus();
// detect client timezone
var dt = new Date(),
tz = dt.getTimezoneOffset() / -60,
stdtz = dt.getStdTimezoneOffset() / -60;
$('#rcmlogintz').val(stdtz);
$('#rcmlogindst').val(tz > stdtz ? 1 : 0);
// display 'loading' message on form submit, lock submit button
$('form').submit(function () {
$('input[type=submit]', this).prop('disabled', true);
rcmail.display_message('', 'loading');
});
this.enable_command('login', true);
break;
default:
break;
}
// prevent from form submit with Enter key in file input fields
if (bw.ie)
$('input[type=file]').keydown(function(e) { if (e.keyCode == '13') e.preventDefault(); });
// flag object as complete
this.loaded = true;
// show message
if (this.pending_message)
this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
// map implicit containers
if (this.gui_objects.folderlist)
this.gui_containers.foldertray = $(this.gui_objects.folderlist);
// trigger init event hook
this.triggerEvent('init', { task:this.task, action:this.env.action });
// execute all foreign onload scripts
// @deprecated
for (var i in this.onloads) {
if (typeof this.onloads[i] === 'string')
eval(this.onloads[i]);
else if (typeof this.onloads[i] === 'function')
this.onloads[i]();
}
// start keep-alive interval
this.start_keepalive();
};
this.log = function(msg)
{
if (window.console && console.log)
console.log(msg);
};
/*********************************************************/
/********* client command interface *********/
/*********************************************************/
// execute a specific command on the web client
this.command = function(command, props, obj)
{
var ret, uid, cid, url, flag;
if (obj && obj.blur)
obj.blur();
if (this.busy)
return false;
// command not supported or allowed
if (!this.commands[command]) {
// pass command to parent window
if (this.is_framed())
parent.rcmail.command(command, props);
return false;
}
// check input before leaving compose step
if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands)<0) {
if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
return false;
}
// process external commands
if (typeof this.command_handlers[command] === 'function') {
ret = this.command_handlers[command](props, obj);
return ret !== undefined ? ret : (obj ? false : true);
}
else if (typeof this.command_handlers[command] === 'string') {
ret = window[this.command_handlers[command]](props, obj);
return ret !== undefined ? ret : (obj ? false : true);
}
// trigger plugin hooks
this.triggerEvent('actionbefore', {props:props, action:command});
ret = this.triggerEvent('before'+command, props);
if (ret !== undefined) {
// abort if one of the handlers returned false
if (ret === false)
return false;
else
props = ret;
}
ret = undefined;
// process internal command
switch (command) {
case 'login':
if (this.gui_objects.loginform)
this.gui_objects.loginform.submit();
break;
// commands to switch task
case 'mail':
case 'addressbook':
case 'settings':
case 'logout':
this.switch_task(command);
break;
case 'about':
location.href = '?_task=settings&_action=about';
break;
case 'permaurl':
if (obj && obj.href && obj.target)
return true;
else if (this.env.permaurl)
parent.location.href = this.env.permaurl;
break;
case 'menu-open':
case 'menu-save':
this.triggerEvent(command, {props:props});
return false;
case 'open':
if (uid = this.get_single_uid()) {
obj.href = '?_task='+this.env.task+'&_action=show&_mbox='+urlencode(this.env.mailbox)+'&_uid='+uid;
return true;
}
break;
case 'list':
this.reset_qsearch();
if (this.task == 'mail') {
this.list_mailbox(props);
if (this.env.trash_mailbox && !this.env.flag_for_deletion)
this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage');
}
else if (this.task == 'addressbook') {
this.list_contacts(props);
}
break;
case 'load-headers':
this.load_headers(obj);
break;
case 'sort':
var sort_order, sort_col = props;
if (this.env.sort_col==sort_col)
sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
else
sort_order = 'ASC';
// set table header and update env
this.set_list_sorting(sort_col, sort_order);
// reload message list
this.list_mailbox('', '', sort_col+'_'+sort_order);
break;
case 'nextpage':
this.list_page('next');
break;
case 'lastpage':
this.list_page('last');
break;
case 'previouspage':
this.list_page('prev');
break;
case 'firstpage':
this.list_page('first');
break;
case 'expunge':
if (this.env.messagecount)
this.expunge_mailbox(this.env.mailbox);
break;
case 'purge':
case 'empty-mailbox':
if (this.env.messagecount)
this.purge_mailbox(this.env.mailbox);
break;
// common commands used in multiple tasks
case 'show':
if (this.task == 'mail') {
uid = this.get_single_uid();
if (uid && (!this.env.uid || uid != this.env.uid)) {
if (this.env.mailbox == this.env.drafts_mailbox)
this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
else
this.show_message(uid);
}
}
else if (this.task == 'addressbook') {
cid = props ? props : this.get_single_cid();
if (cid && !(this.env.action == 'show' && cid == this.env.cid))
this.load_contact(cid, 'show');
}
break;
case 'add':
if (this.task == 'addressbook')
this.load_contact(0, 'add');
else if (this.task == 'settings') {
this.identity_list.clear_selection();
this.load_identity(0, 'add-identity');
}
break;
case 'edit':
if (this.task=='addressbook' && (cid = this.get_single_cid()))
this.load_contact(cid, 'edit');
else if (this.task=='settings' && props)
this.load_identity(props, 'edit-identity');
else if (this.task=='mail' && (cid = this.get_single_uid())) {
url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid=';
this.goto_url('compose', url+cid+'&_mbox='+urlencode(this.env.mailbox), true);
}
break;
case 'save':
var input, form = this.gui_objects.editform;
if (form) {
// adv. search
if (this.env.action == 'search') {
}
// user prefs
else if ((input = $("input[name='_pagesize']", form)) && input.length && isNaN(parseInt(input.val()))) {
alert(this.get_label('nopagesizewarning'));
input.focus();
break;
}
// contacts/identities
else {
// reload form
if (props == 'reload') {
form.action += '?_reload=1';
}
else if (this.task == 'settings' && (this.env.identities_level % 2) == 0 &&
(input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
) {
alert(this.get_label('noemailwarning'));
input.focus();
break;
}
// clear empty input fields
$('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
}
// add selected source (on the list)
if (parent.rcmail && parent.rcmail.env.source)
form.action = this.add_url(form.action, '_orig_source', parent.rcmail.env.source);
form.submit();
}
break;
case 'delete':
// mail task
if (this.task == 'mail')
this.delete_messages();
// addressbook task
else if (this.task == 'addressbook')
this.delete_contacts();
// user settings task
else if (this.task == 'settings')
this.delete_identity();
break;
// mail task commands
case 'move':
case 'moveto':
if (this.task == 'mail')
this.move_messages(props);
else if (this.task == 'addressbook' && this.drag_active)
this.copy_contact(null, props);
break;
case 'copy':
if (this.task == 'mail')
this.copy_messages(props);
break;
case 'mark':
if (props)
this.mark_message(props);
break;
case 'toggle_status':
if (props && !props._row)
break;
flag = 'read';
if (props._row.uid) {
uid = props._row.uid;
// toggle read/unread
if (this.message_list.rows[uid].deleted) {
flag = 'undelete';
}
else if (!this.message_list.rows[uid].unread)
flag = 'unread';
}
this.mark_message(flag, uid);
break;
case 'toggle_flag':
if (props && !props._row)
break;
flag = 'flagged';
if (props._row.uid) {
uid = props._row.uid;
// toggle flagged/unflagged
if (this.message_list.rows[uid].flagged)
flag = 'unflagged';
}
this.mark_message(flag, uid);
break;
case 'always-load':
if (this.env.uid && this.env.sender) {
this.add_contact(urlencode(this.env.sender));
window.setTimeout(function(){ ref.command('load-images'); }, 300);
break;
}
case 'load-images':
if (this.env.uid)
this.show_message(this.env.uid, true, this.env.action=='preview');
break;
case 'load-attachment':
var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part;
// open attachment in frame if it's of a supported mimetype
if (this.env.uid && props.mimetype && this.env.mimetypes && $.inArray(props.mimetype, this.env.mimetypes)>=0) {
if (props.mimetype == 'text/html')
qstring += '&_safe=1';
this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment');
if (this.attachment_win) {
window.setTimeout(function(){ ref.attachment_win.focus(); }, 10);
break;
}
}
this.goto_url('get', qstring+'&_download=1', false);
break;
case 'select-all':
this.select_all_mode = props ? false : true;
this.dummy_select = true; // prevent msg opening if there's only one msg on the list
if (props == 'invert')
this.message_list.invert_selection();
else
this.message_list.select_all(props == 'page' ? '' : props);
this.dummy_select = null;
break;
case 'select-none':
this.select_all_mode = false;
this.message_list.clear_selection();
break;
case 'expand-all':
this.env.autoexpand_threads = 1;
this.message_list.expand_all();
break;
case 'expand-unread':
this.env.autoexpand_threads = 2;
this.message_list.collapse_all();
this.expand_unread();
break;
case 'collapse-all':
this.env.autoexpand_threads = 0;
this.message_list.collapse_all();
break;
case 'nextmessage':
if (this.env.next_uid)
this.show_message(this.env.next_uid, false, this.env.action=='preview');
break;
case 'lastmessage':
if (this.env.last_uid)
this.show_message(this.env.last_uid);
break;
case 'previousmessage':
if (this.env.prev_uid)
this.show_message(this.env.prev_uid, false, this.env.action=='preview');
break;
case 'firstmessage':
if (this.env.first_uid)
this.show_message(this.env.first_uid);
break;
case 'checkmail':
this.check_for_recent(true);
break;
case 'compose':
url = this.url('mail/compose');
if (this.task == 'mail') {
url += '&_mbox='+urlencode(this.env.mailbox);
if (props)
url += '&_to='+urlencode(props);
}
// modify url if we're in addressbook
else if (this.task == 'addressbook') {
// switch to mail compose step directly
if (props && props.indexOf('@') > 0) {
url = this.get_task_url('mail', url);
this.redirect(url + '&_to='+urlencode(props));
break;
}
// use contact_id passed as command parameter
var n, len, a_cids = [];
if (props)
a_cids.push(props);
// get selected contacts
else if (this.contact_list) {
var selection = this.contact_list.get_selection();
for (n=0, len=selection.length; n<len; n++)
a_cids.push(selection[n]);
}
if (a_cids.length)
this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source}, true);
else if (this.env.group)
this.http_post('mailto', { _gid: this.env.group, _source: this.env.source}, true);
break;
}
else if (props)
url += '&_to='+urlencode(props);
this.redirect(url);
break;
case 'spellcheck':
if (window.tinyMCE && tinyMCE.get(this.env.composebody)) {
tinyMCE.execCommand('mceSpellCheck', true);
}
else if (this.env.spellcheck && this.env.spellcheck.spellCheck && this.spellcheck_ready) {
this.env.spellcheck.spellCheck();
this.set_spellcheck_state('checking');
}
break;
case 'savedraft':
// Reset the auto-save timer
self.clearTimeout(this.save_timer);
if (!this.gui_objects.messageform)
break;
// if saving Drafts is disabled in main.inc.php
// or if compose form did not change
if (!this.env.drafts_mailbox || this.cmp_hash == this.compose_field_hash())
break;
var form = this.gui_objects.messageform,
msgid = this.set_busy(true, 'savingmessage');
form.target = "savetarget";
form._draft.value = '1';
form.action = this.add_url(form.action, '_unlock', msgid);
form.submit();
break;
case 'send':
if (!this.gui_objects.messageform)
break;
if (!props.nocheck && !this.check_compose_input(command))
break;
// Reset the auto-save timer
self.clearTimeout(this.save_timer);
// all checks passed, send message
var lang = this.spellcheck_lang(),
form = this.gui_objects.messageform,
msgid = this.set_busy(true, 'sendingmessage');
form.target = 'savetarget';
form._draft.value = '';
form.action = this.add_url(form.action, '_unlock', msgid);
form.action = this.add_url(form.action, '_lang', lang);
form.submit();
// clear timeout (sending could take longer)
clearTimeout(this.request_timer);
break;
case 'send-attachment':
// Reset the auto-save timer
self.clearTimeout(this.save_timer);
this.upload_file(props || this.gui_objects.uploadform);
break;
case 'insert-sig':
this.change_identity($("[name='_from']")[0], true);
break;
case 'reply-all':
case 'reply-list':
case 'reply':
if (uid = this.get_single_uid()) {
url = '_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
if (command == 'reply-all')
// do reply-list, when list is detected and popup menu wasn't used
url += '&_all=' + (!props && this.commands['reply-list'] ? 'list' : 'all');
else if (command == 'reply-list')
url += '&_all=list';
this.goto_url('compose', url, true);
}
break;
case 'forward-attachment':
case 'forward':
if (uid = this.get_single_uid()) {
url = '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
if (command == 'forward-attachment' || (!props && this.env.forward_attachment))
url += '&_attachment=1';
this.goto_url('compose', url, true);
}
break;
case 'print':
if (uid = this.get_single_uid()) {
ref.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
if (this.printwin) {
window.setTimeout(function(){ ref.printwin.focus(); }, 20);
if (this.env.action != 'show')
this.mark_message('read', uid);
}
}
break;
case 'viewsource':
if (uid = this.get_single_uid()) {
ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox));
if (this.sourcewin)
window.setTimeout(function(){ ref.sourcewin.focus(); }, 20);
}
break;
case 'download':
if (uid = this.get_single_uid())
this.goto_url('viewsource', '&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+'&_save=1');
break;
// quicksearch
case 'search':
if (!props && this.gui_objects.qsearchbox)
props = this.gui_objects.qsearchbox.value;
if (props) {
this.qsearch(props);
break;
}
// reset quicksearch
case 'reset-search':
var n, s = this.env.search_request || this.env.qsearch;
this.reset_qsearch();
this.select_all_mode = false;
if (s && this.env.mailbox)
this.list_mailbox(this.env.mailbox, 1);
else if (s && this.task == 'addressbook') {
if (this.env.source == '') {
for (n in this.env.address_sources) break;
this.env.source = n;
this.env.group = '';
}
this.list_contacts(this.env.source, this.env.group, 1);
}
break;
case 'listgroup':
this.reset_qsearch();
this.list_contacts(props.source, props.id);
break;
case 'import':
if (this.env.action == 'import' && this.gui_objects.importform) {
var file = document.getElementById('rcmimportfile');
if (file && !file.value) {
alert(this.get_label('selectimportfile'));
break;
}
this.gui_objects.importform.submit();
this.set_busy(true, 'importwait');
this.lock_form(this.gui_objects.importform, true);
}
else
this.goto_url('import', (this.env.source ? '_target='+urlencode(this.env.source)+'&' : ''));
break;
case 'export':
if (this.contact_list.rowcount > 0) {
this.goto_url('export', { _source:this.env.source, _gid:this.env.group, _search:this.env.search_request });
}
break;
case 'upload-photo':
this.upload_contact_photo(props || this.gui_objects.uploadform);
break;
case 'delete-photo':
this.replace_contact_photo('-del-');
break;
// user settings commands
case 'preferences':
case 'identities':
case 'folders':
this.goto_url('settings/' + command);
break;
case 'undo':
this.http_request('undo', '', this.display_message('', 'loading'));
break;
// unified command call (command name == function name)
default:
var func = command.replace(/-/g, '_');
if (this[func] && typeof this[func] === 'function') {
ret = this[func](props);
}
break;
}
if (this.triggerEvent('after'+command, props) === false)
ret = false;
this.triggerEvent('actionafter', {props:props, action:command});
return ret === false ? false : obj ? false : true;
};
// set command(s) enabled or disabled
this.enable_command = function()
{
var i, n, args = Array.prototype.slice.call(arguments),
enable = args.pop(), cmd;
for (n=0; n<args.length; n++) {
cmd = args[n];
// argument of type array
if (typeof cmd === 'string') {
this.commands[cmd] = enable;
this.set_button(cmd, (enable ? 'act' : 'pas'));
}
// push array elements into commands array
else {
for (i in cmd)
args.push(cmd[i]);
}
}
};
// lock/unlock interface
this.set_busy = function(a, message, id)
{
if (a && message) {
var msg = this.get_label(message);
if (msg == message)
msg = 'Loading...';
id = this.display_message(msg, 'loading');
}
else if (!a && id) {
this.hide_message(id);
}
this.busy = a;
//document.body.style.cursor = a ? 'wait' : 'default';
if (this.gui_objects.editform)
this.lock_form(this.gui_objects.editform, a);
// clear pending timer
if (this.request_timer)
clearTimeout(this.request_timer);
// set timer for requests
if (a && this.env.request_timeout)
this.request_timer = window.setTimeout(function(){ ref.request_timed_out(); }, this.env.request_timeout * 1000);
return id;
};
// return a localized string
this.get_label = function(name, domain)
{
if (domain && this.labels[domain+'.'+name])
return this.labels[domain+'.'+name];
else if (this.labels[name])
return this.labels[name];
else
return name;
};
// alias for convenience reasons
this.gettext = this.get_label;
// switch to another application task
this.switch_task = function(task)
{
if (this.task===task && task!='mail')
return;
var url = this.get_task_url(task);
if (task=='mail')
url += '&_mbox=INBOX';
this.redirect(url);
};
this.get_task_url = function(task, url)
{
if (!url)
url = this.env.comm_path;
return url.replace(/_task=[a-z]+/, '_task='+task);
};
// called when a request timed out
this.request_timed_out = function()
{
this.set_busy(false);
this.display_message('Request timed out!', 'error');
};
this.reload = function(delay)
{
if (this.is_framed())
parent.rcmail.reload(delay);
else if (delay)
window.setTimeout(function(){ rcmail.reload(); }, delay);
else if (window.location)
location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : '');
};
// Add variable to GET string, replace old value if exists
this.add_url = function(url, name, value)
{
value = urlencode(value);
if (/(\?.*)$/.test(url)) {
var urldata = RegExp.$1,
datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
if (datax.test(urldata)) {
urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
}
else
urldata += '&' + name + '=' + value
return url.replace(/(\?.*)$/, urldata);
}
else
return url + '?' + name + '=' + value;
};
this.is_framed = function()
{
return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command);
};
this.save_pref = function(prop)
{
var request = {'_name': prop.name, '_value': prop.value};
if (prop.session)
request['_session'] = prop.session;
if (prop.env)
this.env[prop.env] = prop.value;
this.http_post('save-pref', request);
};
this.html_identifier = function(str, encode)
{
str = String(str);
if (encode)
return Base64.encode(str).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
else
return str.replace(this.identifier_expr, '_');
};
this.html_identifier_decode = function(str)
{
str = String(str).replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
return Base64.decode(str);
};
/*********************************************************/
/********* event handling methods *********/
/*********************************************************/
this.drag_menu = function(e, target)
{
var modkey = rcube_event.get_modifier(e),
menu = this.gui_objects.message_dragmenu;
if (menu && modkey == SHIFT_KEY && this.commands['copy']) {
var pos = rcube_event.get_mouse_pos(e);
this.env.drag_target = target;
$(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show();
return true;
}
return false;
};
this.drag_menu_action = function(action)
{
var menu = this.gui_objects.message_dragmenu;
if (menu) {
$(menu).hide();
}
this.command(action, this.env.drag_target);
this.env.drag_target = null;
};
this.drag_start = function(list)
{
var model = this.task == 'mail' ? this.env.mailboxes : this.env.contactfolders;
this.drag_active = true;
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
// save folderlist and folders location/sizes for droptarget calculation in drag_move()
if (this.gui_objects.folderlist && model) {
this.initialBodyScrollTop = bw.ie ? 0 : window.pageYOffset;
this.initialListScrollTop = this.gui_objects.folderlist.parentNode.scrollTop;
var k, li, height,
list = $(this.gui_objects.folderlist);
pos = list.offset();
this.env.folderlist_coords = { x1:pos.left, y1:pos.top, x2:pos.left + list.width(), y2:pos.top + list.height() };
this.env.folder_coords = [];
for (k in model) {
if (li = this.get_folder_li(k)) {
// only visible folders
if (height = li.firstChild.offsetHeight) {
pos = $(li.firstChild).offset();
this.env.folder_coords[k] = { x1:pos.left, y1:pos.top,
x2:pos.left + li.firstChild.offsetWidth, y2:pos.top + height, on:0 };
}
}
}
}
};
this.drag_end = function(e)
{
this.drag_active = false;
this.env.last_folder_target = null;
if (this.folder_auto_timer) {
window.clearTimeout(this.folder_auto_timer);
this.folder_auto_timer = null;
this.folder_auto_expand = null;
}
// over the folders
if (this.gui_objects.folderlist && this.env.folder_coords) {
for (var k in this.env.folder_coords) {
if (this.env.folder_coords[k].on)
$(this.get_folder_li(k)).removeClass('droptarget');
}
}
};
this.drag_move = function(e)
{
if (this.gui_objects.folderlist && this.env.folder_coords) {
var k, li, div, check, oldclass,
layerclass = 'draglayernormal',
mouse = rcube_event.get_mouse_pos(e),
pos = this.env.folderlist_coords,
// offsets to compensate for scrolling while dragging a message
boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop,
moffset = this.initialListScrollTop-this.gui_objects.folderlist.parentNode.scrollTop;
if (this.contact_list && this.contact_list.draglayer)
oldclass = this.contact_list.draglayer.attr('class');
mouse.y += -moffset-boffset;
// if mouse pointer is outside of folderlist
if (mouse.x < pos.x1 || mouse.x >= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) {
if (this.env.last_folder_target) {
$(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
this.env.folder_coords[this.env.last_folder_target].on = 0;
this.env.last_folder_target = null;
}
if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer)
this.contact_list.draglayer.attr('class', layerclass);
return;
}
// over the folders
for (k in this.env.folder_coords) {
pos = this.env.folder_coords[k];
if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2){
if ((check = this.check_droptarget(k))) {
li = this.get_folder_li(k);
div = $(li.getElementsByTagName('div')[0]);
// if the folder is collapsed, expand it after 1sec and restart the drag & drop process.
if (div.hasClass('collapsed')) {
if (this.folder_auto_timer)
window.clearTimeout(this.folder_auto_timer);
this.folder_auto_expand = this.env.mailboxes[k].id;
this.folder_auto_timer = window.setTimeout(function() {
rcmail.command('collapse-folder', rcmail.folder_auto_expand);
rcmail.drag_start(null);
}, 1000);
} else if (this.folder_auto_timer) {
window.clearTimeout(this.folder_auto_timer);
this.folder_auto_timer = null;
this.folder_auto_expand = null;
}
$(li).addClass('droptarget');
this.env.folder_coords[k].on = 1;
this.env.last_folder_target = k;
layerclass = 'draglayer' + (check > 1 ? 'copy' : 'normal');
} else { // Clear target, otherwise drag end will trigger move into last valid droptarget
this.env.last_folder_target = null;
}
}
else if (pos.on) {
$(this.get_folder_li(k)).removeClass('droptarget');
this.env.folder_coords[k].on = 0;
}
}
if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer)
this.contact_list.draglayer.attr('class', layerclass);
}
};
this.collapse_folder = function(name)
{
var li = this.get_folder_li(name, '', true),
div = $('div:first', li),
ul = $('ul:first', li);
if (div.hasClass('collapsed')) {
ul.show();
div.removeClass('collapsed').addClass('expanded');
var reg = new RegExp('&'+urlencode(name)+'&');
this.env.collapsed_folders = this.env.collapsed_folders.replace(reg, '');
}
else if (div.hasClass('expanded')) {
ul.hide();
div.removeClass('expanded').addClass('collapsed');
this.env.collapsed_folders = this.env.collapsed_folders+'&'+urlencode(name)+'&';
// select parent folder if one of its childs is currently selected
if (this.env.mailbox.indexOf(name + this.env.delimiter) == 0)
this.command('list', name);
}
else
return;
// Work around a bug in IE6 and IE7, see #1485309
if (bw.ie6 || bw.ie7) {
var siblings = li.nextSibling ? li.nextSibling.getElementsByTagName('ul') : null;
if (siblings && siblings.length && (li = siblings[0]) && li.style && li.style.display != 'none') {
li.style.display = 'none';
li.style.display = '';
}
}
this.command('save-pref', { name: 'collapsed_folders', value: this.env.collapsed_folders });
this.set_unread_count_display(name, false);
};
this.doc_mouse_up = function(e)
{
var model, list, li, id;
// ignore event if jquery UI dialog is open
if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
return;
if (list = this.message_list) {
if (!rcube_mouse_is_over(e, list.list.parentNode))
list.blur();
else
list.focus();
model = this.env.mailboxes;
}
else if (list = this.contact_list) {
if (!rcube_mouse_is_over(e, list.list.parentNode))
list.blur();
else
list.focus();
model = this.env.contactfolders;
}
else if (this.ksearch_value) {
this.ksearch_blur();
}
// handle mouse release when dragging
if (this.drag_active && model && this.env.last_folder_target) {
var target = model[this.env.last_folder_target];
$(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
this.env.last_folder_target = null;
list.draglayer.hide();
if (!this.drag_menu(e, target))
this.command('moveto', target);
}
// reset 'pressed' buttons
if (this.buttons_sel) {
for (id in this.buttons_sel)
if (typeof id !== 'function')
this.button_out(this.buttons_sel[id], id);
this.buttons_sel = {};
}
};
this.click_on_list = function(e)
{
if (this.gui_objects.qsearchbox)
this.gui_objects.qsearchbox.blur();
if (this.message_list)
this.message_list.focus();
else if (this.contact_list)
this.contact_list.focus();
return true;
};
this.msglist_select = function(list)
{
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
var selected = list.get_single_selection() != null;
this.enable_command(this.env.message_commands, selected);
if (selected) {
// Hide certain command buttons when Drafts folder is selected
if (this.env.mailbox == this.env.drafts_mailbox)
this.enable_command('reply', 'reply-all', 'reply-list', 'forward', 'forward-attachment', false);
// Disable reply-list when List-Post header is not set
else {
var msg = this.env.messages[list.get_single_selection()];
if (!msg.ml)
this.enable_command('reply-list', false);
}
}
// Multi-message commands
this.enable_command('delete', 'moveto', 'copy', 'mark', (list.selection.length > 0 ? true : false));
// reset all-pages-selection
if (selected || (list.selection.length && list.selection.length != list.rowcount))
this.select_all_mode = false;
// start timer for message preview (wait for double click)
if (selected && this.env.contentframe && !list.multi_selecting && !this.dummy_select)
this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200);
else if (this.env.contentframe)
this.show_contentframe(false);
};
// This allow as to re-select selected message and display it in preview frame
this.msglist_click = function(list)
{
if (list.multi_selecting || !this.env.contentframe)
return;
if (list.get_single_selection() && window.frames && window.frames[this.env.contentframe]) {
if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)>=0) {
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200);
}
}
};
this.msglist_dbl_click = function(list)
{
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
var uid = list.get_single_selection();
if (uid && this.env.mailbox == this.env.drafts_mailbox)
this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
else if (uid)
this.show_message(uid, false, false);
};
this.msglist_keypress = function(list)
{
if (list.modkey == CONTROL_KEY)
return;
if (list.key_pressed == list.ENTER_KEY)
this.command('show');
else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
this.command('delete');
else if (list.key_pressed == 33)
this.command('previouspage');
else if (list.key_pressed == 34)
this.command('nextpage');
};
this.msglist_get_preview = function()
{
var uid = this.get_single_uid();
if (uid && this.env.contentframe && !this.drag_active)
this.show_message(uid, false, true);
else if (this.env.contentframe)
this.show_contentframe(false);
};
this.msglist_expand = function(row)
{
if (this.env.messages[row.uid])
this.env.messages[row.uid].expanded = row.expanded;
};
this.msglist_set_coltypes = function(list)
{
var i, found, name, cols = list.list.tHead.rows[0].cells;
this.env.coltypes = [];
for (i=0; i<cols.length; i++)
if (cols[i].id && cols[i].id.match(/^rcm/)) {
name = cols[i].id.replace(/^rcm/, '');
this.env.coltypes.push(name == 'to' ? 'from' : name);
}
if ((found = $.inArray('flag', this.env.coltypes)) >= 0)
this.env.flagged_col = found;
if ((found = $.inArray('subject', this.env.coltypes)) >= 0)
this.env.subject_col = found;
this.command('save-pref', { name: 'list_cols', value: this.env.coltypes, session: 'list_attrib/columns' });
};
this.check_droptarget = function(id)
{
var allow = false, copy = false;
if (this.task == 'mail')
allow = (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual);
else if (this.task == 'settings')
allow = (id != this.env.mailbox);
else if (this.task == 'addressbook') {
if (id != this.env.source && this.env.contactfolders[id]) {
if (this.env.contactfolders[id].type == 'group') {
var target_abook = this.env.contactfolders[id].source;
allow = this.env.contactfolders[id].id != this.env.group && !this.env.contactfolders[target_abook].readonly;
copy = target_abook != this.env.source;
}
else {
allow = !this.env.contactfolders[id].readonly;
copy = true;
}
}
}
return allow ? (copy ? 2 : 1) : 0;
};
/*********************************************************/
/********* (message) list functionality *********/
/*********************************************************/
this.init_message_row = function(row)
{
var expando, self = this, uid = row.uid,
status_icon = (this.env.status_col != null ? 'status' : 'msg') + 'icn' + row.uid;
if (uid && this.env.messages[uid])
$.extend(row, this.env.messages[uid]);
// set eventhandler to status icon
if (row.icon = document.getElementById(status_icon)) {
row.icon._row = row.obj;
row.icon.onmousedown = function(e) { self.command('toggle_status', this); rcube_event.cancel(e); };
}
// save message icon position too
if (this.env.status_col != null)
row.msgicon = document.getElementById('msgicn'+row.uid);
else
row.msgicon = row.icon;
// set eventhandler to flag icon, if icon found
if (this.env.flagged_col != null && (row.flagicon = document.getElementById('flagicn'+row.uid))) {
row.flagicon._row = row.obj;
row.flagicon.onmousedown = function(e) { self.command('toggle_flag', this); rcube_event.cancel(e); };
}
if (!row.depth && row.has_children && (expando = document.getElementById('rcmexpando'+row.uid))) {
row.expando = expando;
expando.onmousedown = function(e) { return self.expand_message_row(e, uid); };
}
this.triggerEvent('insertrow', { uid:uid, row:row });
};
// create a table row in the message list
this.add_message_row = function(uid, cols, flags, attop)
{
if (!this.gui_objects.messagelist || !this.message_list)
return false;
// Prevent from adding messages from different folder (#1487752)
if (flags.mbox != this.env.mailbox && !flags.skip_mbox_check)
return false;
if (!this.env.messages[uid])
this.env.messages[uid] = {};
// merge flags over local message object
$.extend(this.env.messages[uid], {
deleted: flags.deleted?1:0,
replied: flags.answered?1:0,
unread: !flags.seen?1:0,
forwarded: flags.forwarded?1:0,
flagged: flags.flagged?1:0,
has_children: flags.has_children?1:0,
depth: flags.depth?flags.depth:0,
unread_children: flags.unread_children?flags.unread_children:0,
parent_uid: flags.parent_uid?flags.parent_uid:0,
selected: this.select_all_mode || this.message_list.in_selection(uid),
ml: flags.ml?1:0,
ctype: flags.ctype,
// flags from plugins
flags: flags.extra_flags
});
var c, n, col, html, tree = '', expando = '',
list = this.message_list,
rows = list.rows,
message = this.env.messages[uid],
css_class = 'message'
+ (!flags.seen ? ' unread' : '')
+ (flags.deleted ? ' deleted' : '')
+ (flags.flagged ? ' flagged' : '')
+ (flags.unread_children && flags.seen && !this.env.autoexpand_threads ? ' unroot' : '')
+ (message.selected ? ' selected' : ''),
// for performance use DOM instead of jQuery here
row = document.createElement('tr');
row.id = 'rcmrow'+uid;
row.className = css_class;
// message status icons
css_class = 'msgicon';
if (this.env.status_col === null) {
css_class += ' status';
if (flags.deleted)
css_class += ' deleted';
else if (!flags.seen)
css_class += ' unread';
else if (flags.unread_children > 0)
css_class += ' unreadchildren';
}
if (flags.answered)
css_class += ' replied';
if (flags.forwarded)
css_class += ' forwarded';
// update selection
if (message.selected && !list.in_selection(uid))
list.selection.push(uid);
// threads
if (this.env.threading) {
if (message.depth) {
// This assumes that div width is hardcoded to 15px,
tree += '<span id="rcmtab' + uid + '" class="branch" style="width:' + (message.depth * 15) + 'px;">&nbsp;&nbsp;</span>';
if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
|| ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
(!rows[message.parent_uid] || !rows[message.parent_uid].expanded))
) {
row.style.display = 'none';
message.expanded = false;
}
else
message.expanded = true;
}
else if (message.has_children) {
if (message.expanded === undefined && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) {
message.expanded = true;
}
expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
}
}
tree += '<span id="msgicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
// build subject link
if (!bw.ie && cols.subject) {
var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show';
var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid';
cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+uid+'"'+
' onclick="return rcube_event.cancel(event)" onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')">'+cols.subject+'</a>';
}
// add each submitted col
for (n in this.env.coltypes) {
c = this.env.coltypes[n];
col = document.createElement('td');
col.className = String(c).toLowerCase();
if (c == 'flag') {
css_class = (flags.flagged ? 'flagged' : 'unflagged');
html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
}
else if (c == 'attachment') {
if (/application\/|multipart\/m/.test(flags.ctype))
html = '<span class="attachment">&nbsp;</span>';
else if (/multipart\/report/.test(flags.ctype))
html = '<span class="report">&nbsp;</span>';
else
html = '&nbsp;';
}
else if (c == 'status') {
if (flags.deleted)
css_class = 'deleted';
else if (!flags.seen)
css_class = 'unread';
else if (flags.unread_children > 0)
css_class = 'unreadchildren';
else
css_class = 'msgicon';
html = '<span id="statusicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
}
else if (c == 'threads')
html = expando;
else if (c == 'subject') {
if (bw.ie)
col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); };
html = tree + cols[c];
}
else if (c == 'priority') {
if (flags.prio > 0 && flags.prio < 6)
html = '<span class="prio'+flags.prio+'">&nbsp;</span>';
else
html = '&nbsp;';
}
else
html = cols[c];
col.innerHTML = html;
row.appendChild(col);
}
list.insert_row(row, attop);
// remove 'old' row
if (attop && this.env.pagesize && list.rowcount > this.env.pagesize) {
var uid = list.get_last_row();
list.remove_row(uid);
list.clear_selection(uid);
}
};
this.set_list_sorting = function(sort_col, sort_order)
{
// set table header class
$('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
if (sort_col)
$('#rcm'+sort_col).addClass('sorted'+sort_order);
this.env.sort_col = sort_col;
this.env.sort_order = sort_order;
};
this.set_list_options = function(cols, sort_col, sort_order, threads)
{
var update, add_url = '';
if (sort_col === undefined)
sort_col = this.env.sort_col;
if (!sort_order)
sort_order = this.env.sort_order;
if (this.env.sort_col != sort_col || this.env.sort_order != sort_order) {
update = 1;
this.set_list_sorting(sort_col, sort_order);
}
if (this.env.threading != threads) {
update = 1;
add_url += '&_threads=' + threads;
}
if (cols && cols.length) {
// make sure new columns are added at the end of the list
var i, idx, name, newcols = [], oldcols = this.env.coltypes;
for (i=0; i<oldcols.length; i++) {
name = oldcols[i] == 'to' ? 'from' : oldcols[i];
idx = $.inArray(name, cols);
if (idx != -1) {
newcols.push(name);
delete cols[idx];
}
}
for (i=0; i<cols.length; i++)
if (cols[i])
newcols.push(cols[i]);
if (newcols.join() != oldcols.join()) {
update = 1;
add_url += '&_cols=' + newcols.join(',');
}
}
if (update)
this.list_mailbox('', '', sort_col+'_'+sort_order, add_url);
};
// when user doble-clicks on a row
this.show_message = function(id, safe, preview)
{
if (!id)
return;
var target = window,
action = preview ? 'preview': 'show',
url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox);
if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
target = window.frames[this.env.contentframe];
url += '&_framed=1';
}
if (safe)
url += '&_safe=1';
// also send search request to get the right messages
if (this.env.search_request)
url += '&_search='+this.env.search_request;
if (action == 'preview' && String(target.location.href).indexOf(url) >= 0)
this.show_contentframe(true);
else {
this.location_href(this.env.comm_path+url, target, true);
// mark as read and change mbox unread counter
if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
this.preview_read_timer = window.setTimeout(function() {
ref.set_message(id, 'unread', false);
ref.update_thread_root(id, 'read');
if (ref.env.unread_counts[ref.env.mailbox]) {
ref.env.unread_counts[ref.env.mailbox] -= 1;
ref.set_unread_count(ref.env.mailbox, ref.env.unread_counts[ref.env.mailbox], ref.env.mailbox == 'INBOX');
}
if (ref.env.preview_pane_mark_read > 0)
ref.http_post('mark', '_uid='+id+'&_flag=read&_quiet=1');
}, this.env.preview_pane_mark_read * 1000);
}
}
};
this.show_contentframe = function(show)
{
var frm, win;
if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length) {
if (!show && (win = window.frames[this.env.contentframe])) {
if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
win.location.href = this.env.blankpage;
}
else if (!bw.safari && !bw.konq)
frm[show ? 'show' : 'hide']();
}
if (!show && this.busy)
this.set_busy(false, null, this.env.frame_lock);
};
this.lock_frame = function()
{
if (!this.env.frame_lock)
(this.is_framed() ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading');
};
// list a specific page
this.list_page = function(page)
{
if (page == 'next')
page = this.env.current_page+1;
else if (page == 'last')
page = this.env.pagecount;
else if (page == 'prev' && this.env.current_page > 1)
page = this.env.current_page-1;
else if (page == 'first' && this.env.current_page > 1)
page = 1;
if (page > 0 && page <= this.env.pagecount) {
this.env.current_page = page;
if (this.task == 'mail')
this.list_mailbox(this.env.mailbox, page);
else if (this.task == 'addressbook')
this.list_contacts(this.env.source, this.env.group, page);
}
};
// list messages of a specific mailbox using filter
this.filter_mailbox = function(filter)
{
var lock = this.set_busy(true, 'searching');
this.clear_message_list();
// reset vars
this.env.current_page = 1;
this.http_request('search', this.search_params(false, filter), lock);
};
// list messages of a specific mailbox
this.list_mailbox = function(mbox, page, sort, add_url)
{
var url = '', target = window;
if (!mbox)
mbox = this.env.mailbox ? this.env.mailbox : 'INBOX';
if (add_url)
url += add_url;
// add sort to url if set
if (sort)
url += '&_sort=' + sort;
// also send search request to get the right messages
if (this.env.search_request)
url += '&_search='+this.env.search_request;
// set page=1 if changeing to another mailbox
if (this.env.mailbox != mbox) {
page = 1;
this.env.current_page = page;
this.select_all_mode = false;
}
// unselect selected messages and clear the list and message data
this.clear_message_list();
if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
url += '&_refresh=1';
this.select_folder(mbox, '', true);
this.unmark_folder(mbox, 'recent', '', true);
this.env.mailbox = mbox;
// load message list remotely
if (this.gui_objects.messagelist) {
this.list_mailbox_remote(mbox, page, url);
return;
}
if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
target = window.frames[this.env.contentframe];
url += '&_framed=1';
}
// load message list to target frame/window
if (mbox) {
this.set_busy(true, 'loading');
this.location_href(this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+url, target);
}
};
this.clear_message_list = function()
{
this.env.messages = {};
this.last_selected = 0;
this.show_contentframe(false);
if (this.message_list)
this.message_list.clear(true);
};
// send remote request to load message list
this.list_mailbox_remote = function(mbox, page, add_url)
{
// clear message list first
this.message_list.clear();
// send request to server
var url = '_mbox='+urlencode(mbox)+(page ? '&_page='+page : ''),
lock = this.set_busy(true, 'loading');
this.http_request('list', url+add_url, lock);
};
// removes messages that doesn't exists from list selection array
this.update_selection = function()
{
var selected = this.message_list.selection,
rows = this.message_list.rows,
i, selection = [];
for (i in selected)
if (rows[selected[i]])
selection.push(selected[i]);
this.message_list.selection = selection;
}
// expand all threads with unread children
this.expand_unread = function()
{
var r, tbody = this.gui_objects.messagelist.tBodies[0],
new_row = tbody.firstChild;
while (new_row) {
if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid]) && r.unread_children) {
this.message_list.expand_all(r);
this.set_unread_children(r.uid);
}
new_row = new_row.nextSibling;
}
return false;
};
// thread expanding/collapsing handler
this.expand_message_row = function(e, uid)
{
var row = this.message_list.rows[uid];
// handle unread_children mark
row.expanded = !row.expanded;
this.set_unread_children(uid);
row.expanded = !row.expanded;
this.message_list.expand_row(e, uid);
};
// message list expanding
this.expand_threads = function()
{
if (!this.env.threading || !this.env.autoexpand_threads || !this.message_list)
return;
switch (this.env.autoexpand_threads) {
case 2: this.expand_unread(); break;
case 1: this.message_list.expand_all(); break;
}
};
// Initializes threads indicators/expanders after list update
this.init_threads = function(roots, mbox)
{
// #1487752
if (mbox && mbox != this.env.mailbox)
return false;
for (var n=0, len=roots.length; n<len; n++)
this.add_tree_icons(roots[n]);
this.expand_threads();
};
// adds threads tree icons to the list (or specified thread)
this.add_tree_icons = function(root)
{
var i, l, r, n, len, pos, tmp = [], uid = [],
row, rows = this.message_list.rows;
if (root)
row = rows[root] ? rows[root].obj : null;
else
row = this.message_list.list.tBodies[0].firstChild;
while (row) {
if (row.nodeType == 1 && (r = rows[row.uid])) {
if (r.depth) {
for (i=tmp.length-1; i>=0; i--) {
len = tmp[i].length;
if (len > r.depth) {
pos = len - r.depth;
if (!(tmp[i][pos] & 2))
tmp[i][pos] = tmp[i][pos] ? tmp[i][pos]+2 : 2;
}
else if (len == r.depth) {
if (!(tmp[i][0] & 2))
tmp[i][0] += 2;
}
if (r.depth > len)
break;
}
tmp.push(new Array(r.depth));
tmp[tmp.length-1][0] = 1;
uid.push(r.uid);
}
else {
if (tmp.length) {
for (i in tmp) {
this.set_tree_icons(uid[i], tmp[i]);
}
tmp = [];
uid = [];
}
if (root && row != rows[root].obj)
break;
}
}
row = row.nextSibling;
}
if (tmp.length) {
for (i in tmp) {
this.set_tree_icons(uid[i], tmp[i]);
}
}
};
// adds tree icons to specified message row
this.set_tree_icons = function(uid, tree)
{
var i, divs = [], html = '', len = tree.length;
for (i=0; i<len; i++) {
if (tree[i] > 2)
divs.push({'class': 'l3', width: 15});
else if (tree[i] > 1)
divs.push({'class': 'l2', width: 15});
else if (tree[i] > 0)
divs.push({'class': 'l1', width: 15});
// separator div
else if (divs.length && !divs[divs.length-1]['class'])
divs[divs.length-1].width += 15;
else
divs.push({'class': null, width: 15});
}
for (i=divs.length-1; i>=0; i--) {
if (divs[i]['class'])
html += '<div class="tree '+divs[i]['class']+'" />';
else
html += '<div style="width:'+divs[i].width+'px" />';
}
if (html)
$('#rcmtab'+uid).html(html);
};
// update parent in a thread
this.update_thread_root = function(uid, flag)
{
if (!this.env.threading)
return;
var root = this.message_list.find_root(uid);
if (uid == root)
return;
var p = this.message_list.rows[root];
if (flag == 'read' && p.unread_children) {
p.unread_children--;
}
else if (flag == 'unread' && p.has_children) {
// unread_children may be undefined
p.unread_children = p.unread_children ? p.unread_children + 1 : 1;
}
else {
return;
}
this.set_message_icon(root);
this.set_unread_children(root);
};
// update thread indicators for all messages in a thread below the specified message
// return number of removed/added root level messages
this.update_thread = function (uid)
{
if (!this.env.threading)
return 0;
var r, parent, count = 0,
rows = this.message_list.rows,
row = rows[uid],
depth = rows[uid].depth,
roots = [];
if (!row.depth) // root message: decrease roots count
count--;
else if (row.unread) {
// update unread_children for thread root
parent = this.message_list.find_root(uid);
rows[parent].unread_children--;
this.set_unread_children(parent);
}
parent = row.parent_uid;
// childrens
row = row.obj.nextSibling;
while (row) {
if (row.nodeType == 1 && (r = rows[row.uid])) {
if (!r.depth || r.depth <= depth)
break;
r.depth--; // move left
// reset width and clear the content of a tab, icons will be added later
$('#rcmtab'+r.uid).width(r.depth * 15).html('');
if (!r.depth) { // a new root
count++; // increase roots count
r.parent_uid = 0;
if (r.has_children) {
// replace 'leaf' with 'collapsed'
$('#rcmrow'+r.uid+' '+'.leaf:first')
.attr('id', 'rcmexpando' + r.uid)
.attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed'))
.bind('mousedown', {uid:r.uid, p:this},
function(e) { return e.data.p.expand_message_row(e, e.data.uid); });
r.unread_children = 0;
roots.push(r);
}
// show if it was hidden
if (r.obj.style.display == 'none')
$(r.obj).show();
}
else {
if (r.depth == depth)
r.parent_uid = parent;
if (r.unread && roots.length)
roots[roots.length-1].unread_children++;
}
}
row = row.nextSibling;
}
// update unread_children for roots
for (var i=0; i<roots.length; i++)
this.set_unread_children(roots[i].uid);
return count;
};
this.delete_excessive_thread_rows = function()
{
var rows = this.message_list.rows,
tbody = this.message_list.list.tBodies[0],
row = tbody.firstChild,
cnt = this.env.pagesize + 1;
while (row) {
if (row.nodeType == 1 && (r = rows[row.uid])) {
if (!r.depth && cnt)
cnt--;
if (!cnt)
this.message_list.remove_row(row.uid);
}
row = row.nextSibling;
}
};
// set message icon
this.set_message_icon = function(uid)
{
var css_class,
row = this.message_list.rows[uid];
if (!row)
return false;
if (row.icon) {
css_class = 'msgicon';
if (row.deleted)
css_class += ' deleted';
else if (row.unread)
css_class += ' unread';
else if (row.unread_children)
css_class += ' unreadchildren';
if (row.msgicon == row.icon) {
if (row.replied)
css_class += ' replied';
if (row.forwarded)
css_class += ' forwarded';
css_class += ' status';
}
row.icon.className = css_class;
}
if (row.msgicon && row.msgicon != row.icon) {
css_class = 'msgicon';
if (!row.unread && row.unread_children)
css_class += ' unreadchildren';
if (row.replied)
css_class += ' replied';
if (row.forwarded)
css_class += ' forwarded';
row.msgicon.className = css_class;
}
if (row.flagicon) {
css_class = (row.flagged ? 'flagged' : 'unflagged');
row.flagicon.className = css_class;
}
};
// set message status
this.set_message_status = function(uid, flag, status)
{
var row = this.message_list.rows[uid];
if (!row)
return false;
if (flag == 'unread')
row.unread = status;
else if(flag == 'deleted')
row.deleted = status;
else if (flag == 'replied')
row.replied = status;
else if (flag == 'forwarded')
row.forwarded = status;
else if (flag == 'flagged')
row.flagged = status;
};
// set message row status, class and icon
this.set_message = function(uid, flag, status)
{
var row = this.message_list.rows[uid];
if (!row)
return false;
if (flag)
this.set_message_status(uid, flag, status);
var rowobj = $(row.obj);
if (row.unread && !rowobj.hasClass('unread'))
rowobj.addClass('unread');
else if (!row.unread && rowobj.hasClass('unread'))
rowobj.removeClass('unread');
if (row.deleted && !rowobj.hasClass('deleted'))
rowobj.addClass('deleted');
else if (!row.deleted && rowobj.hasClass('deleted'))
rowobj.removeClass('deleted');
if (row.flagged && !rowobj.hasClass('flagged'))
rowobj.addClass('flagged');
else if (!row.flagged && rowobj.hasClass('flagged'))
rowobj.removeClass('flagged');
this.set_unread_children(uid);
this.set_message_icon(uid);
};
// sets unroot (unread_children) class of parent row
this.set_unread_children = function(uid)
{
var row = this.message_list.rows[uid];
if (row.parent_uid)
return;
if (!row.unread && row.unread_children && !row.expanded)
$(row.obj).addClass('unroot');
else
$(row.obj).removeClass('unroot');
};
// copy selected messages to the specified mailbox
this.copy_messages = function(mbox)
{
if (mbox && typeof mbox === 'object')
mbox = mbox.id;
// exit if current or no mailbox specified or if selection is empty
if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length)))
return;
var a_uids = [],
lock = this.display_message(this.get_label('copyingmessage'), 'loading'),
add_url = '&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : '');
if (this.env.uid)
a_uids[0] = this.env.uid;
else {
var selection = this.message_list.get_selection();
for (var n in selection) {
a_uids.push(selection[n]);
}
}
add_url += '&_uid='+this.uids_to_list(a_uids);
// send request to server
this.http_post('copy', '_mbox='+urlencode(this.env.mailbox)+add_url, lock);
};
// move selected messages to the specified mailbox
this.move_messages = function(mbox)
{
if (mbox && typeof mbox === 'object')
mbox = mbox.id;
// exit if current or no mailbox specified or if selection is empty
if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length)))
return;
var lock = false,
add_url = '&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : '');
// show wait message
if (this.env.action == 'show') {
lock = this.set_busy(true, 'movingmessage');
}
else
this.show_contentframe(false);
// Hide message command buttons until a message is selected
this.enable_command(this.env.message_commands, false);
this._with_selected_messages('moveto', lock, add_url);
};
// delete selected messages from the current mailbox
this.delete_messages = function()
{
var uid, i, len, trash = this.env.trash_mailbox,
list = this.message_list,
selection = list ? $.merge([], list.get_selection()) : [];
// exit if no mailbox specified or if selection is empty
if (!this.env.uid && !selection.length)
return;
// also select childs of collapsed rows
for (i=0, len=selection.length; i<len; i++) {
uid = selection[i];
if (list.rows[uid].has_children && !list.rows[uid].expanded)
list.select_childs(uid);
}
// if config is set to flag for deletion
if (this.env.flag_for_deletion) {
this.mark_message('delete');
return false;
}
// if there isn't a defined trash mailbox or we are in it
// @TODO: we should check if defined trash mailbox exists
else if (!trash || this.env.mailbox == trash)
this.permanently_remove_messages();
// if there is a trash mailbox defined and we're not currently in it
else {
// if shift was pressed delete it immediately
if (list && list.modkey == SHIFT_KEY) {
if (confirm(this.get_label('deletemessagesconfirm')))
this.permanently_remove_messages();
}
else
this.move_messages(trash);
}
return true;
};
// delete the selected messages permanently
this.permanently_remove_messages = function()
{
// exit if no mailbox specified or if selection is empty
if (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length))
return;
this.show_contentframe(false);
this._with_selected_messages('delete', false, '&_from='+(this.env.action ? this.env.action : ''));
};
// Send a specifc moveto/delete request with UIDs of all selected messages
// @private
this._with_selected_messages = function(action, lock, add_url)
{
var a_uids = [], count = 0, msg;
if (this.env.uid)
a_uids[0] = this.env.uid;
else {
var n, id, root, roots = [],
selection = this.message_list.get_selection();
for (n=0, len=selection.length; n<len; n++) {
id = selection[n];
a_uids.push(id);
if (this.env.threading) {
count += this.update_thread(id);
root = this.message_list.find_root(id);
if (root != id && $.inArray(root, roots) < 0) {
roots.push(root);
}
}
this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
}
// make sure there are no selected rows
if (!this.env.display_next)
this.message_list.clear_selection();
// update thread tree icons
for (n=0, len=roots.length; n<len; n++) {
this.add_tree_icons(roots[n]);
}
}
// also send search request to get the right messages
if (this.env.search_request)
add_url += '&_search='+this.env.search_request;
if (this.env.display_next && this.env.next_uid)
add_url += '&_next_uid='+this.env.next_uid;
if (count < 0)
add_url += '&_count='+(count*-1);
else if (count > 0)
// remove threads from the end of the list
this.delete_excessive_thread_rows();
add_url += '&_uid='+this.uids_to_list(a_uids);
if (!lock) {
msg = action == 'moveto' ? 'movingmessage' : 'deletingmessage';
lock = this.display_message(this.get_label(msg), 'loading');
}
// send request to server
this.http_post(action, '_mbox='+urlencode(this.env.mailbox)+add_url, lock);
};
// set a specific flag to one or more messages
this.mark_message = function(flag, uid)
{
var a_uids = [], r_uids = [], len, n, id,
selection = this.message_list ? this.message_list.get_selection() : [];
if (uid)
a_uids[0] = uid;
else if (this.env.uid)
a_uids[0] = this.env.uid;
else if (this.message_list) {
for (n=0, len=selection.length; n<len; n++) {
a_uids.push(selection[n]);
}
}
if (!this.message_list)
r_uids = a_uids;
else
for (n=0, len=a_uids.length; n<len; n++) {
id = a_uids[n];
if ((flag=='read' && this.message_list.rows[id].unread)
|| (flag=='unread' && !this.message_list.rows[id].unread)
|| (flag=='delete' && !this.message_list.rows[id].deleted)
|| (flag=='undelete' && this.message_list.rows[id].deleted)
|| (flag=='flagged' && !this.message_list.rows[id].flagged)
|| (flag=='unflagged' && this.message_list.rows[id].flagged))
{
r_uids.push(id);
}
}
// nothing to do
if (!r_uids.length && !this.select_all_mode)
return;
switch (flag) {
case 'read':
case 'unread':
this.toggle_read_status(flag, r_uids);
break;
case 'delete':
case 'undelete':
this.toggle_delete_status(r_uids);
break;
case 'flagged':
case 'unflagged':
this.toggle_flagged_status(flag, a_uids);
break;
}
};
// set class to read/unread
this.toggle_read_status = function(flag, a_uids)
{
var i, len = a_uids.length,
url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
lock = this.display_message(this.get_label('markingmessage'), 'loading');
// mark all message rows as read/unread
for (i=0; i<len; i++)
this.set_message(a_uids[i], 'unread', (flag=='unread' ? true : false));
// also send search request to get the right messages
if (this.env.search_request)
url += '&_search='+this.env.search_request;
this.http_post('mark', url, lock);
for (i=0; i<len; i++)
this.update_thread_root(a_uids[i], flag);
};
// set image to flagged or unflagged
this.toggle_flagged_status = function(flag, a_uids)
{
var i, len = a_uids.length,
url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
lock = this.display_message(this.get_label('markingmessage'), 'loading');
// mark all message rows as flagged/unflagged
for (i=0; i<len; i++)
this.set_message(a_uids[i], 'flagged', (flag=='flagged' ? true : false));
// also send search request to get the right messages
if (this.env.search_request)
url += '&_search='+this.env.search_request;
this.http_post('mark', url, lock);
};
// mark all message rows as deleted/undeleted
this.toggle_delete_status = function(a_uids)
{
var len = a_uids.length,
i, uid, all_deleted = true,
rows = this.message_list ? this.message_list.rows : [];
if (len == 1) {
if (!rows.length || (rows[a_uids[0]] && !rows[a_uids[0]].deleted))
this.flag_as_deleted(a_uids);
else
this.flag_as_undeleted(a_uids);
return true;
}
for (i=0; i<len; i++) {
uid = a_uids[i];
if (rows[uid] && !rows[uid].deleted) {
all_deleted = false;
break;
}
}
if (all_deleted)
this.flag_as_undeleted(a_uids);
else
this.flag_as_deleted(a_uids);
return true;
};
this.flag_as_undeleted = function(a_uids)
{
var i, len=a_uids.length,
url = '_uid='+this.uids_to_list(a_uids)+'&_flag=undelete',
lock = this.display_message(this.get_label('markingmessage'), 'loading');
for (i=0; i<len; i++)
this.set_message(a_uids[i], 'deleted', false);
// also send search request to get the right messages
if (this.env.search_request)
url += '&_search='+this.env.search_request;
this.http_post('mark', url, lock);
return true;
};
this.flag_as_deleted = function(a_uids)
{
var add_url = '',
r_uids = [],
rows = this.message_list ? this.message_list.rows : [],
count = 0;
for (var i=0, len=a_uids.length; i<len; i++) {
uid = a_uids[i];
if (rows[uid]) {
if (rows[uid].unread)
r_uids[r_uids.length] = uid;
if (this.env.skip_deleted) {
count += this.update_thread(uid);
this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1));
}
else
this.set_message(uid, 'deleted', true);
}
}
// make sure there are no selected rows
if (this.env.skip_deleted && this.message_list) {
if(!this.env.display_next)
this.message_list.clear_selection();
if (count < 0)
add_url += '&_count='+(count*-1);
else if (count > 0)
// remove threads from the end of the list
this.delete_excessive_thread_rows();
}
add_url = '&_from='+(this.env.action ? this.env.action : ''),
lock = this.display_message(this.get_label('markingmessage'), 'loading');
// ??
if (r_uids.length)
add_url += '&_ruid='+this.uids_to_list(r_uids);
if (this.env.skip_deleted) {
if (this.env.display_next && this.env.next_uid)
add_url += '&_next_uid='+this.env.next_uid;
}
// also send search request to get the right messages
if (this.env.search_request)
add_url += '&_search='+this.env.search_request;
this.http_post('mark', '_uid='+this.uids_to_list(a_uids)+'&_flag=delete'+add_url, lock);
return true;
};
// flag as read without mark request (called from backend)
// argument should be a coma-separated list of uids
this.flag_deleted_as_read = function(uids)
{
var icn_src, uid, i, len,
rows = this.message_list ? this.message_list.rows : [];
uids = String(uids).split(',');
for (i=0, len=uids.length; i<len; i++) {
uid = uids[i];
if (rows[uid])
this.set_message(uid, 'unread', false);
}
};
// Converts array of message UIDs to comma-separated list for use in URL
// with select_all mode checking
this.uids_to_list = function(uids)
{
return this.select_all_mode ? '*' : uids.join(',');
};
/*********************************************************/
/********* mailbox folders methods *********/
/*********************************************************/
this.expunge_mailbox = function(mbox)
{
var lock, url = '_mbox='+urlencode(mbox);
// lock interface if it's the active mailbox
if (mbox == this.env.mailbox) {
lock = this.set_busy(true, 'loading');
url += '&_reload=1';
if (this.env.search_request)
url += '&_search='+this.env.search_request;
}
// send request to server
this.http_post('expunge', url, lock);
};
this.purge_mailbox = function(mbox)
{
var lock = false,
url = '_mbox='+urlencode(mbox);
if (!confirm(this.get_label('purgefolderconfirm')))
return false;
// lock interface if it's the active mailbox
if (mbox == this.env.mailbox) {
lock = this.set_busy(true, 'loading');
url += '&_reload=1';
}
// send request to server
this.http_post('purge', url, lock);
};
// test if purge command is allowed
this.purge_mailbox_test = function()
{
return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox
|| this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter))
|| this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter))));
};
/*********************************************************/
/********* login form methods *********/
/*********************************************************/
// handler for keyboard events on the _user field
this.login_user_keyup = function(e)
{
var key = rcube_event.get_keycode(e);
var passwd = $('#rcmloginpwd');
// enter
if (key == 13 && passwd.length && !passwd.val()) {
passwd.focus();
return rcube_event.cancel(e);
}
return true;
};
/*********************************************************/
/********* message compose methods *********/
/*********************************************************/
// init message compose form: set focus and eventhandlers
this.init_messageform = function()
{
if (!this.gui_objects.messageform)
return false;
var input_from = $("[name='_from']"),
input_to = $("[name='_to']"),
input_subject = $("input[name='_subject']"),
input_message = $("[name='_message']").get(0),
html_mode = $("input[name='_is_html']").val() == '1',
ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
ac_props;
// configure parallel autocompletion
if (this.env.autocomplete_threads > 0) {
ac_props = {
threads: this.env.autocomplete_threads,
sources: this.env.autocomplete_sources
};
}
// init live search events
this.init_address_input_events(input_to, ac_props);
for (var i in ac_fields) {
this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
}
if (!html_mode) {
this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
// add signature according to selected identity
// if we have HTML editor, signature is added in callback
if (input_from.prop('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '') {
this.change_identity(input_from[0]);
}
}
if (input_to.val() == '')
input_to.focus();
else if (input_subject.val() == '')
input_subject.focus();
else if (input_message)
input_message.focus();
this.env.compose_focus_elem = document.activeElement;
// get summary of all field values
this.compose_field_hash(true);
// start the auto-save timer
this.auto_save_start();
};
this.init_address_input_events = function(obj, props)
{
this.env.recipients_delimiter = this.env.recipients_separator + ' ';
obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, props); })
.attr('autocomplete', 'off');
};
// checks the input fields before sending a message
this.check_compose_input = function(cmd)
{
// check input fields
var ed, input_to = $("[name='_to']"),
input_cc = $("[name='_cc']"),
input_bcc = $("[name='_bcc']"),
input_from = $("[name='_from']"),
input_subject = $("[name='_subject']"),
input_message = $("[name='_message']");
// check sender (if have no identities)
if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
alert(this.get_label('nosenderwarning'));
input_from.focus();
return false;
}
// check for empty recipient
var recipients = input_to.val() ? input_to.val() : (input_cc.val() ? input_cc.val() : input_bcc.val());
if (!rcube_check_email(recipients.replace(/^\s+/, '').replace(/[\s,;]+$/, ''), true)) {
alert(this.get_label('norecipientwarning'));
input_to.focus();
return false;
}
// check if all files has been uploaded
for (var key in this.env.attachments) {
if (typeof this.env.attachments[key] === 'object' && !this.env.attachments[key].complete) {
alert(this.get_label('notuploadedwarning'));
return false;
}
}
// display localized warning for missing subject
if (input_subject.val() == '') {
var myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>').appendTo(document.body);
var prompt_value = $('<input>').attr('type', 'text').attr('size', 30).appendTo(myprompt).val(this.get_label('nosubject'));
var buttons = {};
buttons[this.get_label('cancel')] = function(){
input_subject.focus();
$(this).dialog('close');
};
buttons[this.get_label('sendmessage')] = function(){
input_subject.val(prompt_value.val());
$(this).dialog('close');
ref.command(cmd, { nocheck:true }); // repeat command which triggered this
};
myprompt.dialog({
modal: true,
resizable: false,
buttons: buttons,
close: function(event, ui) { $(this).remove() }
});
prompt_value.select();
return false;
}
// Apply spellcheck changes if spell checker is active
this.stop_spellchecking();
if (window.tinyMCE)
ed = tinyMCE.get(this.env.composebody);
// check for empty body
if (!ed && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) {
input_message.focus();
return false;
}
else if (ed) {
if (!ed.getContent() && !confirm(this.get_label('nobodywarning'))) {
ed.focus();
return false;
}
// move body from html editor to textarea (just to be sure, #1485860)
tinyMCE.triggerSave();
}
return true;
};
this.toggle_editor = function(props)
{
if (props.mode == 'html') {
this.display_spellcheck_controls(false);
this.plain2html($('#'+props.id).val(), props.id);
tinyMCE.execCommand('mceAddControl', false, props.id);
if (this.env.default_font)
window.setTimeout(function() {
$(tinyMCE.get(props.id).getBody()).css('font-family', rcmail.env.default_font);
}, 500);
}
else {
var thisMCE = tinyMCE.get(props.id), existingHtml;
if (thisMCE.plugins.spellchecker && thisMCE.plugins.spellchecker.active)
thisMCE.execCommand('mceSpellCheck', false);
if (existingHtml = thisMCE.getContent()) {
if (!confirm(this.get_label('editorwarning'))) {
return false;
}
this.html2plain(existingHtml, props.id);
}
tinyMCE.execCommand('mceRemoveControl', false, props.id);
this.display_spellcheck_controls(true);
}
return true;
};
this.stop_spellchecking = function()
{
var ed;
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) {
if (ed.plugins.spellchecker && ed.plugins.spellchecker.active)
ed.execCommand('mceSpellCheck');
}
else if ((ed = this.env.spellcheck) && !this.spellcheck_ready) {
$(ed.spell_span).trigger('click');
this.set_spellcheck_state('ready');
}
};
this.display_spellcheck_controls = function(vis)
{
if (this.env.spellcheck) {
// stop spellchecking process
if (!vis)
this.stop_spellchecking();
$(this.env.spellcheck.spell_container)[vis ? 'show' : 'hide']();
}
};
this.set_spellcheck_state = function(s)
{
this.spellcheck_ready = (s == 'ready' || s == 'no_error_found');
this.enable_command('spellcheck', this.spellcheck_ready);
};
// get selected language
this.spellcheck_lang = function()
{
var ed;
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins.spellchecker) {
return ed.plugins.spellchecker.selectedLang;
}
else if (this.env.spellcheck) {
return GOOGIE_CUR_LANG;
}
};
// resume spellchecking, highlight provided mispellings without new ajax request
this.spellcheck_resume = function(ishtml, data)
{
if (ishtml) {
var ed = tinyMCE.get(this.env.composebody);
sp = ed.plugins.spellchecker;
sp.active = 1;
sp._markWords(data);
ed.nodeChanged();
}
else {
var sp = this.env.spellcheck;
sp.prepare(false, true);
sp.processData(data);
}
}
this.set_draft_id = function(id)
{
$("input[name='_draft_saveid']").val(id);
};
this.auto_save_start = function()
{
if (this.env.draft_autosave)
this.save_timer = self.setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000);
// Unlock interface now that saving is complete
this.busy = false;
};
this.compose_field_hash = function(save)
{
// check input fields
var ed, str = '',
value_to = $("[name='_to']").val(),
value_cc = $("[name='_cc']").val(),
value_bcc = $("[name='_bcc']").val(),
value_subject = $("[name='_subject']").val();
if (value_to)
str += value_to+':';
if (value_cc)
str += value_cc+':';
if (value_bcc)
str += value_bcc+':';
if (value_subject)
str += value_subject+':';
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)))
str += ed.getContent();
else
str += $("[name='_message']").val();
if (this.env.attachments)
for (var upload_id in this.env.attachments)
str += upload_id;
if (save)
this.cmp_hash = str;
return str;
};
this.change_identity = function(obj, show_sig)
{
if (!obj || !obj.options)
return false;
if (!show_sig)
show_sig = this.env.show_sig;
var cursor_pos, p = -1,
id = obj.options[obj.selectedIndex].value,
input_message = $("[name='_message']"),
message = input_message.val(),
is_html = ($("input[name='_is_html']").val() == '1'),
sig = this.env.identity,
sig_separator = this.env.sig_above && (this.env.compose_mode == 'reply' || this.env.compose_mode == 'forward') ? '---' : '-- ';
// enable manual signature insert
if (this.env.signatures && this.env.signatures[id]) {
this.enable_command('insert-sig', true);
this.env.compose_commands.push('insert-sig');
}
else
this.enable_command('insert-sig', false);
if (!is_html) {
// remove the 'old' signature
if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) {
sig = this.env.signatures[sig].is_html ? this.env.signatures[sig].plain_text : this.env.signatures[sig].text;
sig = sig.replace(/\r\n/g, '\n');
if (!sig.match(/^--[ -]\n/m))
sig = sig_separator + '\n' + sig;
p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
if (p >= 0)
message = message.substring(0, p) + message.substring(p+sig.length, message.length);
}
// add the new signature string
if (show_sig && this.env.signatures && this.env.signatures[id]) {
sig = this.env.signatures[id]['is_html'] ? this.env.signatures[id]['plain_text'] : this.env.signatures[id]['text'];
sig = sig.replace(/\r\n/g, '\n');
if (!sig.match(/^--[ -]\n/m))
sig = sig_separator + '\n' + sig;
if (this.env.sig_above) {
if (p >= 0) { // in place of removed signature
message = message.substring(0, p) + sig + message.substring(p, message.length);
cursor_pos = p - 1;
}
else if (pos = this.get_caret_pos(input_message.get(0))) { // at cursor position
message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length);
cursor_pos = pos;
}
else { // on top
cursor_pos = 0;
message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, '');
}
}
else {
message = message.replace(/[\r\n]+$/, '');
cursor_pos = !this.env.top_posting && message.length ? message.length+1 : 0;
message += '\n\n' + sig;
}
}
else
cursor_pos = this.env.top_posting ? 0 : message.length;
input_message.val(message);
// move cursor before the signature
this.set_caret_pos(input_message.get(0), cursor_pos);
}
else if (show_sig && this.env.signatures) { // html
var editor = tinyMCE.get(this.env.composebody),
sigElem = editor.dom.get('_rc_sig');
// Append the signature as a div within the body
if (!sigElem) {
var body = editor.getBody(),
doc = editor.getDoc();
sigElem = doc.createElement('div');
sigElem.setAttribute('id', '_rc_sig');
if (this.env.sig_above) {
// if no existing sig and top posting then insert at caret pos
editor.getWin().focus(); // correct focus in IE & Chrome
var node = editor.selection.getNode();
if (node.nodeName == 'BODY') {
// no real focus, insert at start
body.insertBefore(sigElem, body.firstChild);
body.insertBefore(doc.createElement('br'), body.firstChild);
}
else {
body.insertBefore(sigElem, node.nextSibling);
body.insertBefore(doc.createElement('br'), node.nextSibling);
}
}
else {
if (bw.ie) // add empty line before signature on IE
body.appendChild(doc.createElement('br'));
body.appendChild(sigElem);
}
}
if (this.env.signatures[id]) {
if (this.env.signatures[id].is_html) {
sig = this.env.signatures[id].text;
if (!this.env.signatures[id].plain_text.match(/^--[ -]\r?\n/m))
sig = sig_separator + '<br />' + sig;
}
else {
sig = this.env.signatures[id].text;
if (!sig.match(/^--[ -]\r?\n/m))
sig = sig_separator + '\n' + sig;
sig = '<pre>' + sig + '</pre>';
}
sigElem.innerHTML = sig;
}
}
this.env.identity = id;
return true;
};
// upload attachment file
this.upload_file = function(form)
{
if (!form)
return false;
// get file input field, count files on capable browser
var i, size = 0, field = $('input[type=file]', form).get(0),
files = field.files ? field.files.length : field.value ? 1 : 0;
// create hidden iframe and post upload form
if (files) {
// check file size
if (field.files && this.env.max_filesize && this.env.filesizeerror) {
for (i=0; i<files; i++)
size += field.files[i].size;
if (size && size > this.env.max_filesize) {
this.display_message(this.env.filesizeerror, 'error');
return;
}
}
var frame_name = this.async_upload_form(form, 'upload', function(e) {
var d, content = '';
try {
if (this.contentDocument) {
d = this.contentDocument;
} else if (this.contentWindow) {
d = this.contentWindow.document;
}
content = d.childNodes[0].innerHTML;
} catch (err) {}
if (!content.match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) {
if (!content.match(/display_message/))
rcmail.display_message(rcmail.get_label('fileuploaderror'), 'error');
rcmail.remove_from_attachment_list(e.data.ts);
}
// Opera hack: handle double onload
if (bw.opera)
rcmail.env.uploadframe = e.data.ts;
});
// display upload indicator and cancel button
var content = '<span>' + this.get_label('uploading' + (files > 1 ? 'many' : '')) + '</span>',
ts = frame_name.replace(/^rcmupload/, '');
if (this.env.loadingicon)
content = '<img src="'+this.env.loadingicon+'" alt="" class="uploading" />'+content;
if (this.env.cancelicon)
content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload" class="cancelupload"><img src="'+this.env.cancelicon+'" alt="" /></a>'+content;
this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false });
// upload progress support
if (this.env.upload_progress_time) {
this.upload_progress_start('upload', ts);
}
}
// set reference to the form object
this.gui_objects.attachmentform = form;
return true;
};
// add file name to attachment list
// called from upload page
this.add2attachment_list = function(name, att, upload_id)
{
if (!this.gui_objects.attachmentlist)
return false;
var indicator, li = $('<li>').attr('id', name).addClass(att.classname).html(att.html);
// replace indicator's li
if (upload_id && (indicator = document.getElementById(upload_id))) {
li.replaceAll(indicator);
}
else { // add new li
li.appendTo(this.gui_objects.attachmentlist);
}
if (upload_id && this.env.attachments[upload_id])
delete this.env.attachments[upload_id];
this.env.attachments[name] = att;
return true;
};
this.remove_from_attachment_list = function(name)
{
delete this.env.attachments[name];
$('#'+name).remove();
};
this.remove_attachment = function(name)
{
if (name && this.env.attachments[name])
this.http_post('remove-attachment', { _id:this.env.compose_id, _file:name });
return true;
};
this.cancel_attachment_upload = function(name, frame_name)
{
if (!name || !frame_name)
return false;
this.remove_from_attachment_list(name);
$("iframe[name='"+frame_name+"']").remove();
return false;
};
this.upload_progress_start = function(action, name)
{
window.setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
this.env.upload_progress_time * 1000);
};
this.upload_progress_update = function(param)
{
var elem = $('#'+param.name + '> span');
if (!elem.length || !param.text)
return;
elem.text(param.text);
if (!param.done)
this.upload_progress_start(param.action, param.name);
};
// send remote request to add a new contact
this.add_contact = function(value)
{
if (value)
this.http_post('addcontact', '_address='+value);
return true;
};
// send remote request to search mail or contacts
this.qsearch = function(value)
{
if (value != '') {
var n, lock = this.set_busy(true, 'searching');
if (this.message_list)
this.clear_message_list();
else if (this.contact_list)
this.list_contacts_clear();
// reset vars
this.env.current_page = 1;
r = this.http_request('search', this.search_params(value)
+ (this.env.source ? '&_source='+urlencode(this.env.source) : '')
+ (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
this.env.qsearch = {lock: lock, request: r};
}
};
// build URL params for search
this.search_params = function(search, filter)
{
var n, url = [], mods_arr = [],
mods = this.env.search_mods,
mbox = this.env.mailbox;
if (!filter && this.gui_objects.search_filter)
filter = this.gui_objects.search_filter.value;
if (!search && this.gui_objects.qsearchbox)
search = this.gui_objects.qsearchbox.value;
if (filter)
url.push('_filter=' + urlencode(filter));
if (search) {
url.push('_q='+urlencode(search));
if (mods && this.message_list)
mods = mods[mbox] ? mods[mbox] : mods['*'];
if (mods) {
for (n in mods)
mods_arr.push(n);
url.push('_headers='+mods_arr.join(','));
}
}
if (mbox)
url.push('_mbox='+urlencode(mbox));
return url.join('&');
};
// reset quick-search form
this.reset_qsearch = function()
{
if (this.gui_objects.qsearchbox)
this.gui_objects.qsearchbox.value = '';
if (this.env.qsearch)
this.abort_request(this.env.qsearch);
this.env.qsearch = null;
this.env.search_request = null;
this.env.search_id = null;
};
this.sent_successfully = function(type, msg)
{
this.display_message(msg, type);
// before redirect we need to wait some time for Chrome (#1486177)
window.setTimeout(function(){ ref.list_mailbox(); }, 500);
};
/*********************************************************/
/********* keyboard live-search methods *********/
/*********************************************************/
// handler for keyboard events on address-fields
this.ksearch_keydown = function(e, obj, props)
{
if (this.ksearch_timer)
clearTimeout(this.ksearch_timer);
var highlight,
key = rcube_event.get_keycode(e),
mod = rcube_event.get_modifier(e);
switch (key) {
case 38: // key up
case 40: // key down
if (!this.ksearch_pane)
break;
var dir = key==38 ? 1 : 0;
highlight = document.getElementById('rcmksearchSelected');
if (!highlight)
highlight = this.ksearch_pane.__ul.firstChild;
if (highlight)
this.ksearch_select(dir ? highlight.previousSibling : highlight.nextSibling);
return rcube_event.cancel(e);
case 9: // tab
if (mod == SHIFT_KEY || !this.ksearch_visible()) {
this.ksearch_hide();
return;
}
case 13: // enter
if (!this.ksearch_visible())
return false;
// insert selected address and hide ksearch pane
this.insert_recipient(this.ksearch_selected);
this.ksearch_hide();
return rcube_event.cancel(e);
case 27: // escape
this.ksearch_hide();
return;
case 37: // left
case 39: // right
if (mod != SHIFT_KEY)
return;
}
// start timer
this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
this.ksearch_input = obj;
return true;
};
this.ksearch_visible = function()
{
return (this.ksearch_selected !== null && this.ksearch_selected !== undefined && this.ksearch_value);
};
this.ksearch_select = function(node)
{
var current = $('#rcmksearchSelected');
if (current[0] && node) {
current.removeAttr('id').removeClass('selected');
}
if (node) {
$(node).attr('id', 'rcmksearchSelected').addClass('selected');
this.ksearch_selected = node._rcm_id;
}
};
this.insert_recipient = function(id)
{
if (id === null || !this.env.contacts[id] || !this.ksearch_input)
return;
// get cursor pos
var inp_value = this.ksearch_input.value,
cpos = this.get_caret_pos(this.ksearch_input),
p = inp_value.lastIndexOf(this.ksearch_value, cpos),
trigger = false,
insert = '',
// replace search string with full address
pre = inp_value.substring(0, p),
end = inp_value.substring(p+this.ksearch_value.length, inp_value.length);
this.ksearch_destroy();
// insert all members of a group
if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].id) {
insert += this.env.contacts[id].name + this.env.recipients_delimiter;
this.group2expand = $.extend({}, this.env.contacts[id]);
this.group2expand.input = this.ksearch_input;
this.http_request('mail/group-expand', '_source='+urlencode(this.env.contacts[id].source)+'&_gid='+urlencode(this.env.contacts[id].id), false);
}
else if (typeof this.env.contacts[id] === 'string') {
insert = this.env.contacts[id] + this.env.recipients_delimiter;
trigger = true;
}
this.ksearch_input.value = pre + insert + end;
// set caret to insert pos
cpos = p+insert.length;
if (this.ksearch_input.setSelectionRange)
this.ksearch_input.setSelectionRange(cpos, cpos);
if (trigger)
this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert });
};
this.replace_group_recipients = function(id, recipients)
{
if (this.group2expand && this.group2expand.id == id) {
this.group2expand.input.value = this.group2expand.input.value.replace(this.group2expand.name, recipients);
this.triggerEvent('autocomplete_insert', { field:this.group2expand.input, insert:recipients });
this.group2expand = null;
}
};
// address search processor
this.ksearch_get_results = function(props)
{
var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
if (inp_value === null)
return;
if (this.ksearch_pane && this.ksearch_pane.is(":visible"))
this.ksearch_pane.hide();
// get string from current cursor pos to last comma
var cpos = this.get_caret_pos(this.ksearch_input),
p = inp_value.lastIndexOf(this.env.recipients_separator, cpos-1),
q = inp_value.substring(p+1, cpos),
min = this.env.autocomplete_min_length,
ac = this.ksearch_data;
// trim query string
q = $.trim(q);
// Don't (re-)search if the last results are still active
if (q == this.ksearch_value)
return;
this.ksearch_destroy();
if (q.length && q.length < min) {
if (!this.ksearch_info) {
this.ksearch_info = this.display_message(
this.get_label('autocompletechars').replace('$min', min));
}
return;
}
var old_value = this.ksearch_value;
this.ksearch_value = q;
// ...string is empty
if (!q.length)
return;
// ...new search value contains old one and previous search was not finished or its result was empty
if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || !ac.num) && this.env.contacts && !this.env.contacts.length)
return;
var i, lock, source, xhr, reqid = new Date().getTime(),
threads = props && props.threads ? props.threads : 1,
sources = props && props.sources ? props.sources : [],
action = props && props.action ? props.action : 'mail/autocomplete';
this.ksearch_data = {id: reqid, sources: sources.slice(), action: action,
locks: [], requests: [], num: sources.length};
for (i=0; i<threads; i++) {
source = this.ksearch_data.sources.shift();
if (threads > 1 && source === null)
break;
lock = this.display_message(this.get_label('searching'), 'loading');
xhr = this.http_post(action, '_search='+urlencode(q)+'&_id='+reqid
+ (source ? '&_source='+urlencode(source) : ''), lock);
this.ksearch_data.locks.push(lock);
this.ksearch_data.requests.push(xhr);
}
};
this.ksearch_query_results = function(results, search, reqid)
{
// search stopped in meantime?
if (!this.ksearch_value)
return;
// ignore this outdated search response
if (this.ksearch_input && search != this.ksearch_value)
return;
// display search results
var i, len, ul, li, text, init,
value = this.ksearch_value,
data = this.ksearch_data,
maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15;
// create results pane if not present
if (!this.ksearch_pane) {
ul = $('<ul>');
this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane')
.css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
this.ksearch_pane.__ul = ul[0];
}
ul = this.ksearch_pane.__ul;
// remove all search results or add to existing list if parallel search
if (reqid && this.ksearch_pane.data('reqid') == reqid) {
maxlen -= ul.childNodes.length;
}
else {
this.ksearch_pane.data('reqid', reqid);
init = 1;
// reset content
ul.innerHTML = '';
this.env.contacts = [];
// move the results pane right under the input box
var pos = $(this.ksearch_input).offset();
this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px', display: 'none'});
}
// add each result line to list
if (results && (len = results.length)) {
for (i=0; i < len && maxlen > 0; i++) {
text = typeof results[i] === 'object' ? results[i].name : results[i];
li = document.createElement('LI');
li.innerHTML = text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
li.onmouseover = function(){ ref.ksearch_select(this); };
li.onmouseup = function(){ ref.ksearch_click(this) };
li._rcm_id = this.env.contacts.length + i;
ul.appendChild(li);
maxlen -= 1;
}
}
if (ul.childNodes.length) {
this.ksearch_pane.show();
// select the first
if (!this.env.contacts.length) {
$('li:first', ul).attr('id', 'rcmksearchSelected').addClass('selected');
this.ksearch_selected = 0;
}
}
if (len)
this.env.contacts = this.env.contacts.concat(results);
// run next parallel search
if (data.id == reqid) {
data.num--;
if (maxlen > 0 && data.sources.length) {
var lock, xhr, source = data.sources.shift();
if (source) {
lock = this.display_message(this.get_label('searching'), 'loading');
xhr = this.http_post(data.action, '_search='+urlencode(value)+'&_id='+reqid
+'&_source='+urlencode(source), lock);
this.ksearch_data.locks.push(lock);
this.ksearch_data.requests.push(xhr);
}
}
else if (!maxlen) {
if (!this.ksearch_msg)
this.ksearch_msg = this.display_message(this.get_label('autocompletemore'));
// abort pending searches
this.ksearch_abort();
}
}
};
this.ksearch_click = function(node)
{
if (this.ksearch_input)
this.ksearch_input.focus();
this.insert_recipient(node._rcm_id);
this.ksearch_hide();
};
this.ksearch_blur = function()
{
if (this.ksearch_timer)
clearTimeout(this.ksearch_timer);
this.ksearch_input = null;
this.ksearch_hide();
};
this.ksearch_hide = function()
{
this.ksearch_selected = null;
this.ksearch_value = '';
if (this.ksearch_pane)
this.ksearch_pane.hide();
this.ksearch_destroy();
};
// Clears autocomplete data/requests
this.ksearch_destroy = function()
{
this.ksearch_abort();
if (this.ksearch_info)
this.hide_message(this.ksearch_info);
if (this.ksearch_msg)
this.hide_message(this.ksearch_msg);
this.ksearch_data = null;
this.ksearch_info = null;
this.ksearch_msg = null;
}
// Aborts pending autocomplete requests
this.ksearch_abort = function()
{
var i, len, ac = this.ksearch_data;
if (!ac)
return;
for (i=0, len=ac.locks.length; i<len; i++)
this.abort_request({request: ac.requests[i], lock: ac.locks[i]});
};
/*********************************************************/
/********* address book methods *********/
/*********************************************************/
this.contactlist_keypress = function(list)
{
if (list.key_pressed == list.DELETE_KEY)
this.command('delete');
};
this.contactlist_select = function(list)
{
if (this.preview_timer)
clearTimeout(this.preview_timer);
var n, id, sid, ref = this, writable = false,
source = this.env.source ? this.env.address_sources[this.env.source] : null;
if (id = list.get_single_selection())
this.preview_timer = window.setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
else if (this.env.contentframe)
this.show_contentframe(false);
// no source = search result, we'll need to detect if any of
// selected contacts are in writable addressbook to enable edit/delete
if (list.selection.length) {
if (!source) {
for (n in list.selection) {
sid = String(list.selection[n]).replace(/^[^-]+-/, '');
if (sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly) {
writable = true;
break;
}
}
}
else {
writable = !source.readonly;
}
}
this.enable_command('compose', this.env.group || list.selection.length > 0);
this.enable_command('edit', id && writable);
this.enable_command('delete', list.selection.length && writable);
return false;
};
this.list_contacts = function(src, group, page)
{
var folder, add_url = '',
target = window;
if (!src)
src = this.env.source;
if (page && this.current_page == page && src == this.env.source && group == this.env.group)
return false;
if (src != this.env.source) {
page = this.env.current_page = 1;
this.reset_qsearch();
}
else if (group != this.env.group)
page = this.env.current_page = 1;
if (this.env.search_id)
folder = 'S'+this.env.search_id;
else
folder = group ? 'G'+src+group : src;
this.select_folder(folder);
this.env.source = src;
this.env.group = group;
// load contacts remotely
if (this.gui_objects.contactslist) {
this.list_contacts_remote(src, group, page);
return;
}
if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
target = window.frames[this.env.contentframe];
add_url = '&_framed=1';
}
if (group)
add_url += '&_gid='+group;
if (page)
add_url += '&_page='+page;
// also send search request to get the correct listing
if (this.env.search_request)
add_url += '&_search='+this.env.search_request;
this.set_busy(true, 'loading');
this.location_href(this.env.comm_path + (src ? '&_source='+urlencode(src) : '') + add_url, target);
};
// send remote request to load contacts list
this.list_contacts_remote = function(src, group, page)
{
// clear message list first
this.list_contacts_clear();
// send request to server
var url = (src ? '_source='+urlencode(src) : '') + (page ? (src?'&':'') + '_page='+page : ''),
lock = this.set_busy(true, 'loading');
this.env.source = src;
this.env.group = group;
if (group)
url += '&_gid='+group;
// also send search request to get the right messages
if (this.env.search_request)
url += '&_search='+this.env.search_request;
this.http_request('list', url, lock);
};
this.list_contacts_clear = function()
{
this.contact_list.clear(true);
this.show_contentframe(false);
this.enable_command('delete', false);
this.enable_command('compose', this.env.group ? true : false);
};
// load contact record
this.load_contact = function(cid, action, framed)
{
var add_url = '', target = window;
if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
add_url = '&_framed=1';
target = window.frames[this.env.contentframe];
this.show_contentframe(true);
// load dummy content
if (!cid) {
// unselect selected row(s)
this.contact_list.clear_selection();
this.enable_command('delete', 'compose', false);
}
}
else if (framed)
return false;
if (action && (cid || action=='add') && !this.drag_active) {
if (this.env.group)
add_url += '&_gid='+urlencode(this.env.group);
this.location_href(this.env.comm_path+'&_action='+action
+'&_source='+urlencode(this.env.source)
+'&_cid='+urlencode(cid) + add_url, target, true);
}
return true;
};
// add/delete member to/from the group
this.group_member_change = function(what, cid, source, gid)
{
what = what == 'add' ? 'add' : 'del';
var lock = this.display_message(this.get_label(what == 'add' ? 'addingmember' : 'removingmember'), 'loading');
this.http_post('group-'+what+'members', '_cid='+urlencode(cid)
+ '&_source='+urlencode(source)
+ '&_gid='+urlencode(gid), lock);
};
// copy a contact to the specified target (group or directory)
this.copy_contact = function(cid, to)
{
if (!cid)
cid = this.contact_list.get_selection().join(',');
if (to.type == 'group' && to.source == this.env.source)
this.group_member_change('add', cid, to.source, to.id);
else if (to.type == 'group' && !this.env.address_sources[to.source].readonly) {
var lock = this.display_message(this.get_label('copyingcontact'), 'loading');
this.http_post('copy', '_cid='+urlencode(cid)
+ '&_source='+urlencode(this.env.source)
+ '&_to='+urlencode(to.source)
+ '&_togid='+urlencode(to.id)
+ (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
}
else if (to.id != this.env.source && cid && this.env.address_sources[to.id] && !this.env.address_sources[to.id].readonly) {
var lock = this.display_message(this.get_label('copyingcontact'), 'loading');
this.http_post('copy', '_cid='+urlencode(cid)
+ '&_source='+urlencode(this.env.source)
+ '&_to='+urlencode(to.id)
+ (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
}
};
this.delete_contacts = function()
{
var selection = this.contact_list.get_selection(),
undelete = this.env.source && this.env.address_sources[this.env.source].undelete;
// exit if no mailbox specified or if selection is empty
if (!(selection.length || this.env.cid) || (!undelete && !confirm(this.get_label('deletecontactconfirm'))))
return;
var id, n, a_cids = [], qs = '';
if (this.env.cid)
a_cids.push(this.env.cid);
else {
for (n=0; n<selection.length; n++) {
id = selection[n];
a_cids.push(id);
this.contact_list.remove_row(id, (n == selection.length-1));
}
// hide content frame if we delete the currently displayed contact
if (selection.length == 1)
this.show_contentframe(false);
}
if (this.env.group)
qs += '&_gid='+urlencode(this.env.group);
// also send search request to get the right records from the next page
if (this.env.search_request)
qs += '&_search='+this.env.search_request;
// send request to server
this.http_post('delete', '_cid='+urlencode(a_cids.join(','))
+'&_source='+urlencode(this.env.source)
+'&_from='+(this.env.action ? this.env.action : '')+qs,
this.display_message(this.get_label('contactdeleting'), 'loading'));
return true;
};
// update a contact record in the list
this.update_contact_row = function(cid, cols_arr, newcid, source)
{
var c, row, list = this.contact_list;
cid = this.html_identifier(cid);
// when in searching mode, concat cid with the source name
if (!list.rows[cid]) {
cid = cid+'-'+source;
if (newcid)
newcid = newcid+'-'+source;
}
if (list.rows[cid] && (row = list.rows[cid].obj)) {
for (c=0; c<cols_arr.length; c++)
if (row.cells[c])
$(row.cells[c]).html(cols_arr[c]);
// cid change
if (newcid) {
newcid = this.html_identifier(newcid);
row.id = 'rcmrow' + newcid;
list.remove_row(cid);
list.init_row(row);
list.selection[0] = newcid;
row.style.display = '';
}
}
};
// add row to contacts list
this.add_contact_row = function(cid, cols, select)
{
if (!this.gui_objects.contactslist)
return false;
var c, list = this.contact_list,
row = document.createElement('tr');
row.id = 'rcmrow'+this.html_identifier(cid);
row.className = 'contact';
if (list.in_selection(cid))
row.className += ' selected';
// add each submitted col
for (c in cols) {
col = document.createElement('td');
col.className = String(c).toLowerCase();
col.innerHTML = cols[c];
row.appendChild(col);
}
list.insert_row(row);
this.enable_command('export', list.rowcount > 0);
};
this.init_contact_form = function()
{
var ref = this, col;
this.set_photo_actions($('#ff_photo').val());
for (col in this.env.coltypes)
this.init_edit_field(col, null);
$('.contactfieldgroup .row a.deletebutton').click(function() {
ref.delete_edit_field(this);
return false;
});
$('select.addfieldmenu').change(function(e) {
ref.insert_edit_field($(this).val(), $(this).attr('rel'), this);
this.selectedIndex = 0;
});
// enable date pickers on date fields
if ($.datepicker && this.env.date_format) {
$.datepicker.setDefaults({
dateFormat: this.env.date_format,
changeMonth: true,
changeYear: true,
yearRange: '-100:+10',
showOtherMonths: true,
selectOtherMonths: true,
monthNamesShort: this.env.month_names,
onSelect: function(dateText) { $(this).focus().val(dateText) }
});
$('input.datepicker').datepicker();
}
$("input[type='text']:visible").first().focus();
};
this.group_create = function()
{
this.add_input_row('contactgroup');
};
this.group_rename = function()
{
if (!this.env.group || !this.gui_objects.folderlist)
return;
if (!this.name_input) {
this.enable_command('list', 'listgroup', false);
this.name_input = $('<input>').attr('type', 'text').val(this.env.contactgroups['G'+this.env.source+this.env.group].name);
this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
this.env.group_renaming = true;
var link, li = this.get_folder_li(this.env.source+this.env.group, 'rcmliG');
if (li && (link = li.firstChild)) {
$(link).hide().before(this.name_input);
}
}
this.name_input.select().focus();
};
this.group_delete = function()
{
if (this.env.group && confirm(this.get_label('deletegroupconfirm'))) {
var lock = this.set_busy(true, 'groupdeleting');
this.http_post('group-delete', '_source='+urlencode(this.env.source)+'&_gid='+urlencode(this.env.group), lock);
}
};
// callback from server upon group-delete command
this.remove_group_item = function(prop)
{
var li, key = 'G'+prop.source+prop.id;
if ((li = this.get_folder_li(key))) {
this.triggerEvent('group_delete', { source:prop.source, id:prop.id, li:li });
li.parentNode.removeChild(li);
delete this.env.contactfolders[key];
delete this.env.contactgroups[key];
}
this.list_contacts(prop.source, 0);
};
// @TODO: maybe it would be better to use popup instead of inserting input to the list?
this.add_input_row = function(type)
{
if (!this.gui_objects.folderlist)
return;
if (!this.name_input) {
this.name_input = $('<input>').attr('type', 'text').data('tt', type);
this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
this.name_input_li = $('<li>').addClass(type).append(this.name_input);
var li = type == 'contactsearch' ? $('li:last', this.gui_objects.folderlist) : this.get_folder_li(this.env.source);
this.name_input_li.insertAfter(li);
}
this.name_input.select().focus();
};
// handler for keyboard events on the input field
this.add_input_keydown = function(e)
{
var key = rcube_event.get_keycode(e),
input = $(e.target), itype = input.data('tt');
// enter
if (key == 13) {
var newname = input.val();
if (newname) {
var lock = this.set_busy(true, 'loading');
if (itype == 'contactsearch')
this.http_post('search-create', '_search='+urlencode(this.env.search_request)+'&_name='+urlencode(newname), lock);
else if (this.env.group_renaming)
this.http_post('group-rename', '_source='+urlencode(this.env.source)+'&_gid='+urlencode(this.env.group)+'&_name='+urlencode(newname), lock);
else
this.http_post('group-create', '_source='+urlencode(this.env.source)+'&_name='+urlencode(newname), lock);
}
return false;
}
// escape
else if (key == 27)
this.reset_add_input();
return true;
};
this.reset_add_input = function()
{
if (this.name_input) {
if (this.env.group_renaming) {
var li = this.name_input.parent();
li.children().last().show();
this.env.group_renaming = false;
}
this.name_input.remove();
if (this.name_input_li)
this.name_input_li.remove();
this.name_input = this.name_input_li = null;
}
this.enable_command('list', 'listgroup', true);
};
// callback for creating a new contact group
this.insert_contact_group = function(prop)
{
this.reset_add_input();
prop.type = 'group';
var key = 'G'+prop.source+prop.id,
link = $('<a>').attr('href', '#')
.attr('rel', prop.source+':'+prop.id)
.click(function() { return rcmail.command('listgroup', prop, this); })
.html(prop.name),
li = $('<li>').attr({id: 'rcmli'+this.html_identifier(key), 'class': 'contactgroup'})
.append(link);
this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
this.add_contact_group_row(prop, li);
this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:li[0] });
};
// callback for renaming a contact group
this.update_contact_group = function(prop)
{
this.reset_add_input();
var key = 'G'+prop.source+prop.id,
li = this.get_folder_li(key),
link;
// group ID has changed, replace link node and identifiers
if (li && prop.newid) {
var newkey = 'G'+prop.source+prop.newid,
newprop = $.extend({}, prop);;
li.id = 'rcmli' + this.html_identifier(newkey);
this.env.contactfolders[newkey] = this.env.contactfolders[key];
this.env.contactfolders[newkey].id = prop.newid;
this.env.group = prop.newid;
delete this.env.contactfolders[key];
delete this.env.contactgroups[key];
newprop.id = prop.newid;
newprop.type = 'group';
link = $('<a>').attr('href', '#')
.attr('rel', prop.source+':'+prop.newid)
.click(function() { return rcmail.command('listgroup', newprop, this); })
.html(prop.name);
$(li).children().replaceWith(link);
}
// update displayed group name
else if (li && (link = li.firstChild) && link.tagName.toLowerCase() == 'a')
link.innerHTML = prop.name;
this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;
this.add_contact_group_row(prop, $(li), true);
this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid });
};
// add contact group row to the list, with sorting
this.add_contact_group_row = function(prop, li, reloc)
{
var row, name = prop.name.toUpperCase(),
sibling = this.get_folder_li(prop.source),
prefix = 'rcmliG' + this.html_identifier(prop.source);
// When renaming groups, we need to remove it from DOM and insert it in the proper place
if (reloc) {
row = li.clone(true);
li.remove();
}
else
row = li;
$('li[id^="'+prefix+'"]', this.gui_objects.folderlist).each(function(i, elem) {
if (name >= $(this).text().toUpperCase())
sibling = elem;
else
return false;
});
row.insertAfter(sibling);
};
this.update_group_commands = function()
{
var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null;
this.enable_command('group-create', (source && source.groups && !source.readonly));
this.enable_command('group-rename', 'group-delete', (source && source.groups && this.env.group && !source.readonly));
};
this.init_edit_field = function(col, elem)
{
if (!elem)
elem = $('.ff_' + col);
elem.focus(function(){ ref.focus_textfield(this); })
.blur(function(){ ref.blur_textfield(this); })
.each(function(){ this._placeholder = this.title = (ref.env.coltypes[col].label || ''); ref.blur_textfield(this); });
};
this.insert_edit_field = function(col, section, menu)
{
// just make pre-defined input field visible
var elem = $('#ff_'+col);
if (elem.length) {
elem.show().focus();
$(menu).children('option[value="'+col+'"]').prop('disabled', true);
}
else {
var lastelem = $('.ff_'+col),
appendcontainer = $('#contactsection'+section+' .contactcontroller'+col);
if (!appendcontainer.length)
appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col).insertAfter($('#contactsection'+section+' .contactfieldgroup').last());
if (appendcontainer.length && appendcontainer.get(0).nodeName == 'FIELDSET') {
var input, colprop = this.env.coltypes[col],
row = $('<div>').addClass('row'),
cell = $('<div>').addClass('contactfieldcontent data'),
label = $('<div>').addClass('contactfieldlabel label');
if (colprop.subtypes_select)
label.html(colprop.subtypes_select);
else
label.html(colprop.label);
var name_suffix = colprop.limit != 1 ? '[]' : '';
if (colprop.type == 'text' || colprop.type == 'date') {
input = $('<input>')
.addClass('ff_'+col)
.attr({type: 'text', name: '_'+col+name_suffix, size: colprop.size})
.appendTo(cell);
this.init_edit_field(col, input);
if (colprop.type == 'date' && $.datepicker)
input.datepicker();
}
else if (colprop.type == 'composite') {
var childcol, cp, first, templ, cols = [], suffices = [];
// read template for composite field order
if ((templ = this.env[col+'_template'])) {
for (var j=0; j < templ.length; j++) {
cols.push(templ[j][1]);
suffices.push(templ[j][2]);
}
}
else { // list fields according to appearance in colprop
for (childcol in colprop.childs)
cols.push(childcol);
}
for (var i=0; i < cols.length; i++) {
childcol = cols[i];
cp = colprop.childs[childcol];
input = $('<input>')
.addClass('ff_'+childcol)
.attr({ type: 'text', name: '_'+childcol+name_suffix, size: cp.size })
.appendTo(cell);
cell.append(suffices[i] || " ");
this.init_edit_field(childcol, input);
if (!first) first = input;
}
input = first; // set focus to the first of this composite fields
}
else if (colprop.type == 'select') {
input = $('<select>')
.addClass('ff_'+col)
.attr('name', '_'+col+name_suffix)
.appendTo(cell);
var options = input.attr('options');
options[options.length] = new Option('---', '');
if (colprop.options)
$.each(colprop.options, function(i, val){ options[options.length] = new Option(val, i); });
}
if (input) {
var delbutton = $('<a href="#del"></a>')
.addClass('contactfieldbutton deletebutton')
.attr({title: this.get_label('delete'), rel: col})
.html(this.env.delbutton)
.click(function(){ ref.delete_edit_field(this); return false })
.appendTo(cell);
row.append(label).append(cell).appendTo(appendcontainer.show());
input.first().focus();
// disable option if limit reached
if (!colprop.count) colprop.count = 0;
if (++colprop.count == colprop.limit && colprop.limit)
$(menu).children('option[value="'+col+'"]').prop('disabled', true);
}
}
}
};
this.delete_edit_field = function(elem)
{
var col = $(elem).attr('rel'),
colprop = this.env.coltypes[col],
fieldset = $(elem).parents('fieldset.contactfieldgroup'),
addmenu = fieldset.parent().find('select.addfieldmenu');
// just clear input but don't hide the last field
if (--colprop.count <= 0 && colprop.visible)
$(elem).parent().children('input').val('').blur();
else {
$(elem).parents('div.row').remove();
// hide entire fieldset if no more rows
if (!fieldset.children('div.row').length)
fieldset.hide();
}
// enable option in add-field selector or insert it if necessary
if (addmenu.length) {
var option = addmenu.children('option[value="'+col+'"]');
if (option.length)
option.prop('disabled', false);
else
option = $('<option>').attr('value', col).html(colprop.label).appendTo(addmenu);
addmenu.show();
}
};
this.upload_contact_photo = function(form)
{
if (form && form.elements._photo.value) {
this.async_upload_form(form, 'upload-photo', function(e) {
rcmail.set_busy(false, null, rcmail.photo_upload_id);
});
// display upload indicator
this.photo_upload_id = this.set_busy(true, 'uploading');
}
};
this.replace_contact_photo = function(id)
{
var img_src = id == '-del-' ? this.env.photo_placeholder :
this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + this.env.cid + '&_photo=' + id;
this.set_photo_actions(id);
$(this.gui_objects.contactphoto).children('img').attr('src', img_src);
};
this.photo_upload_end = function()
{
this.set_busy(false, null, this.photo_upload_id);
delete this.photo_upload_id;
};
this.set_photo_actions = function(id)
{
var n, buttons = this.buttons['upload-photo'];
for (n=0; buttons && n < buttons.length; n++)
$('#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto'));
$('#ff_photo').val(id);
this.enable_command('upload-photo', this.env.coltypes.photo ? true : false);
this.enable_command('delete-photo', this.env.coltypes.photo && id != '-del-');
};
// load advanced search page
this.advanced_search = function()
{
var add_url = '&_form=1', target = window;
if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
add_url += '&_framed=1';
target = window.frames[this.env.contentframe];
this.contact_list.clear_selection();
}
this.location_href(this.env.comm_path+'&_action=search'+add_url, target, true);
return true;
};
// unselect directory/group
this.unselect_directory = function()
{
this.select_folder('');
this.enable_command('search-delete', false);
};
// callback for creating a new saved search record
this.insert_saved_search = function(name, id)
{
this.reset_add_input();
var key = 'S'+id,
link = $('<a>').attr('href', '#')
.attr('rel', id)
.click(function() { return rcmail.command('listsearch', id, this); })
.html(name),
li = $('<li>').attr({id: 'rcmli' + this.html_identifier(key), 'class': 'contactsearch'})
.append(link),
prop = {name:name, id:id, li:li[0]};
this.add_saved_search_row(prop, li);
this.select_folder('S'+id);
this.enable_command('search-delete', true);
this.env.search_id = id;
this.triggerEvent('abook_search_insert', prop);
};
// add saved search row to the list, with sorting
this.add_saved_search_row = function(prop, li, reloc)
{
var row, sibling, name = prop.name.toUpperCase();
// When renaming groups, we need to remove it from DOM and insert it in the proper place
if (reloc) {
row = li.clone(true);
li.remove();
}
else
row = li;
$('li[class~="contactsearch"]', this.gui_objects.folderlist).each(function(i, elem) {
if (!sibling)
sibling = this.previousSibling;
if (name >= $(this).text().toUpperCase())
sibling = elem;
else
return false;
});
if (sibling)
row.insertAfter(sibling);
else
row.appendTo(this.gui_objects.folderlist);
};
// creates an input for saved search name
this.search_create = function()
{
this.add_input_row('contactsearch');
};
this.search_delete = function()
{
if (this.env.search_request) {
var lock = this.set_busy(true, 'savedsearchdeleting');
this.http_post('search-delete', '_sid='+urlencode(this.env.search_id), lock);
}
};
// callback from server upon search-delete command
this.remove_search_item = function(id)
{
var li, key = 'S'+id;
if ((li = this.get_folder_li(key))) {
this.triggerEvent('search_delete', { id:id, li:li });
li.parentNode.removeChild(li);
}
this.env.search_id = null;
this.env.search_request = null;
this.list_contacts_clear();
this.reset_qsearch();
this.enable_command('search-delete', 'search-create', false);
};
this.listsearch = function(id)
{
var folder, lock = this.set_busy(true, 'searching');
if (this.contact_list) {
this.list_contacts_clear();
}
this.reset_qsearch();
this.select_folder('S'+id);
// reset vars
this.env.current_page = 1;
this.http_request('search', '_sid='+urlencode(id), lock);
};
/*********************************************************/
/********* user settings methods *********/
/*********************************************************/
// preferences section select and load options frame
this.section_select = function(list)
{
var id = list.get_single_selection(), add_url = '', target = window;
if (id) {
if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
add_url = '&_framed=1';
target = window.frames[this.env.contentframe];
}
this.location_href(this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url, target, true);
}
return true;
};
this.identity_select = function(list)
{
var id;
if (id = list.get_single_selection())
this.load_identity(id, 'edit-identity');
};
// load identity record
this.load_identity = function(id, action)
{
if (action=='edit-identity' && (!id || id==this.env.iid))
return false;
var add_url = '', target = window;
if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
add_url = '&_framed=1';
target = window.frames[this.env.contentframe];
document.getElementById(this.env.contentframe).style.visibility = 'inherit';
}
if (action && (id || action=='add-identity')) {
this.set_busy(true);
this.location_href(this.env.comm_path+'&_action='+action+'&_iid='+id+add_url, target);
}
return true;
};
this.delete_identity = function(id)
{
// exit if no mailbox specified or if selection is empty
var selection = this.identity_list.get_selection();
if (!(selection.length || this.env.iid))
return;
if (!id)
id = this.env.iid ? this.env.iid : selection[0];
// append token to request
this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true);
return true;
};
/*********************************************************/
/********* folder manager methods *********/
/*********************************************************/
this.init_subscription_list = function()
{
var p = this;
this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist,
{multiselect:false, draggable:true, keyboard:false, toggleselect:true});
this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); });
this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
this.subscription_list.row_init = function (row) {
row.obj.onmouseover = function() { p.focus_subscription(row.id); };
row.obj.onmouseout = function() { p.unfocus_subscription(row.id); };
};
this.subscription_list.init();
$('#mailboxroot')
.mouseover(function(){ p.focus_subscription(this.id); })
.mouseout(function(){ p.unfocus_subscription(this.id); })
};
this.focus_subscription = function(id)
{
var row, folder,
delim = RegExp.escape(this.env.delimiter),
reg = RegExp('['+delim+']?[^'+delim+']+$');
if (this.drag_active && this.env.mailbox && (row = document.getElementById(id)))
if (this.env.subscriptionrows[id] &&
(folder = this.env.subscriptionrows[id][0]) !== null
) {
if (this.check_droptarget(folder) &&
!this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] &&
(folder != this.env.mailbox.replace(reg, '')) &&
(!folder.match(new RegExp('^'+RegExp.escape(this.env.mailbox+this.env.delimiter))))
) {
this.env.dstfolder = folder;
$(row).addClass('droptarget');
}
}
};
this.unfocus_subscription = function(id)
{
var row = $('#'+id);
this.env.dstfolder = null;
if (this.env.subscriptionrows[id] && row[0])
row.removeClass('droptarget');
else
$(this.subscription_list.frame).removeClass('droptarget');
};
this.subscription_select = function(list)
{
var id, folder;
if (list && (id = list.get_single_selection()) &&
(folder = this.env.subscriptionrows['rcmrow'+id])
) {
this.env.mailbox = folder[0];
this.show_folder(folder[0]);
this.enable_command('delete-folder', !folder[2]);
}
else {
this.env.mailbox = null;
this.show_contentframe(false);
this.enable_command('delete-folder', 'purge', false);
}
};
this.subscription_move_folder = function(list)
{
var delim = RegExp.escape(this.env.delimiter),
reg = RegExp('['+delim+']?[^'+delim+']+$');
if (this.env.mailbox && this.env.dstfolder !== null && (this.env.dstfolder != this.env.mailbox) &&
(this.env.dstfolder != this.env.mailbox.replace(reg, ''))
) {
reg = new RegExp('[^'+delim+']*['+delim+']', 'g');
var basename = this.env.mailbox.replace(reg, ''),
newname = this.env.dstfolder === '' ? basename : this.env.dstfolder+this.env.delimiter+basename;
if (newname != this.env.mailbox) {
this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.mailbox)+'&_folder_newname='+urlencode(newname), this.set_busy(true, 'foldermoving'));
this.subscription_list.draglayer.hide();
}
}
this.drag_active = false;
this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder));
};
// tell server to create and subscribe a new mailbox
this.create_folder = function()
{
this.show_folder('', this.env.mailbox);
};
// delete a specific mailbox with all its messages
this.delete_folder = function(name)
{
var id = this.get_folder_row_id(name ? name : this.env.mailbox),
folder = this.env.subscriptionrows[id][0];
if (folder && confirm(this.get_label('deletefolderconfirm'))) {
var lock = this.set_busy(true, 'folderdeleting');
this.http_post('delete-folder', '_mbox='+urlencode(folder), lock);
}
};
// Add folder row to the table and initialize it
this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name)
{
if (!this.gui_objects.subscriptionlist)
return false;
var row, n, i, tmp, folders, rowid, list = [], slist = [],
tbody = this.gui_objects.subscriptionlist.tBodies[0],
refrow = $('tr', tbody).get(1),
id = 'rcmrow'+((new Date).getTime());
if (!refrow) {
// Refresh page if we don't have a table row to clone
this.goto_url('folders');
return false;
}
// clone a table row if there are existing rows
row = $(refrow).clone(true);
// set ID, reset css class
row.attr('id', id);
row.attr('class', class_name);
// set folder name
row.find('td:first').html(display_name);
// update subscription checkbox
$('input[name="_subscribed[]"]', row).val(name)
.prop({checked: subscribed ? true : false, disabled: is_protected ? true : false});
// add to folder/row-ID map
this.env.subscriptionrows[id] = [name, display_name, 0];
// sort folders, to find a place where to insert the row
folders = [];
$.each(this.env.subscriptionrows, function(k,v){ folders.push(v) });
folders.sort(function(a,b){ return a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0) });
for (n in folders) {
// protected folder
if (folders[n][2]) {
slist.push(folders[n][0]);
tmp = folders[n][0]+this.env.delimiter;
}
// protected folder's child
else if (tmp && folders[n][0].indexOf(tmp) == 0)
slist.push(folders[n][0]);
// other
else {
list.push(folders[n][0]);
tmp = null;
}
}
// check if subfolder of a protected folder
for (n=0; n<slist.length; n++) {
if (name.indexOf(slist[n]+this.env.delimiter) == 0)
rowid = this.get_folder_row_id(slist[n]);
}
// find folder position after sorting
for (n=0; !rowid && n<list.length; n++) {
if (n && list[n] == name)
rowid = this.get_folder_row_id(list[n-1]);
}
// add row to the table
if (rowid)
$('#'+rowid).after(row);
else
row.appendTo(tbody);
// update list widget
this.subscription_list.clear_selection();
if (!skip_init)
this.init_subscription_list();
row = row.get(0);
if (row.scrollIntoView)
row.scrollIntoView();
return row;
};
// replace an existing table row with a new folder line (with subfolders)
this.replace_folder_row = function(oldfolder, newfolder, display_name, is_protected, class_name)
{
if (!this.gui_objects.subscriptionlist)
return false;
var i, n, len, name, dispname, oldrow, tmprow, row, level,
tbody = this.gui_objects.subscriptionlist.tBodies[0],
folders = this.env.subscriptionrows,
id = this.get_folder_row_id(oldfolder),
regex = new RegExp('^'+RegExp.escape(oldfolder)),
subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'),
// find subfolders of renamed folder
list = this.get_subfolders(oldfolder);
// replace an existing table row
this._remove_folder_row(id);
row = $(this.add_folder_row(newfolder, display_name, is_protected, subscribed, true, class_name));
// detect tree depth change
if (len = list.length) {
level = (oldfolder.split(this.env.delimiter)).length - (newfolder.split(this.env.delimiter)).length;
}
// move subfolders to the new branch
for (n=0; n<len; n++) {
id = list[n];
name = this.env.subscriptionrows[id][0];
dispname = this.env.subscriptionrows[id][1];
oldrow = $('#'+id);
tmprow = oldrow.clone(true);
oldrow.remove();
row.after(tmprow);
row = tmprow;
// update folder index
name = name.replace(regex, newfolder);
$('input[name="_subscribed[]"]', row).val(name);
this.env.subscriptionrows[id][0] = name;
// update the name if level is changed
if (level != 0) {
if (level > 0) {
for (i=level; i>0; i--)
dispname = dispname.replace(/^&nbsp;&nbsp;&nbsp;&nbsp;/, '');
}
else {
for (i=level; i<0; i++)
dispname = '&nbsp;&nbsp;&nbsp;&nbsp;' + dispname;
}
row.find('td:first').html(dispname);
this.env.subscriptionrows[id][1] = dispname;
}
}
// update list widget
this.init_subscription_list();
};
// remove the table row of a specific mailbox from the table
this.remove_folder_row = function(folder, subs)
{
var n, len, list = [], id = this.get_folder_row_id(folder);
// get subfolders if any
if (subs)
list = this.get_subfolders(folder);
// remove old row
this._remove_folder_row(id);
// remove subfolders
for (n=0, len=list.length; n<len; n++)
this._remove_folder_row(list[n]);
};
this._remove_folder_row = function(id)
{
this.subscription_list.remove_row(id.replace(/^rcmrow/, ''));
$('#'+id).remove();
delete this.env.subscriptionrows[id];
}
this.get_subfolders = function(folder)
{
var name, list = [],
regex = new RegExp('^'+RegExp.escape(folder)+RegExp.escape(this.env.delimiter)),
row = $('#'+this.get_folder_row_id(folder)).get(0);
while (row = row.nextSibling) {
if (row.id) {
name = this.env.subscriptionrows[row.id][0];
if (regex.test(name)) {
list.push(row.id);
}
else
break;
}
}
return list;
}
this.subscribe = function(folder)
{
if (folder) {
var lock = this.display_message(this.get_label('foldersubscribing'), 'loading');
this.http_post('subscribe', '_mbox='+urlencode(folder), lock);
}
};
this.unsubscribe = function(folder)
{
if (folder) {
var lock = this.display_message(this.get_label('folderunsubscribing'), 'loading');
this.http_post('unsubscribe', '_mbox='+urlencode(folder), lock);
}
};
// helper method to find a specific mailbox row ID
this.get_folder_row_id = function(folder)
{
var id, folders = this.env.subscriptionrows;
for (id in folders)
if (folders[id] && folders[id][0] == folder)
break;
return id;
};
// when user select a folder in manager
this.show_folder = function(folder, path, force)
{
var target = window,
url = '&_action=edit-folder&_mbox='+urlencode(folder);
if (path)
url += '&_path='+urlencode(path);
if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
target = window.frames[this.env.contentframe];
url += '&_framed=1';
}
if (String(target.location.href).indexOf(url) >= 0 && !force) {
this.show_contentframe(true);
}
else {
this.location_href(this.env.comm_path+url, target, true);
}
};
// disables subscription checkbox (for protected folder)
this.disable_subscription = function(folder)
{
var id = this.get_folder_row_id(folder);
if (id)
$('input[name="_subscribed[]"]', $('#'+id)).prop('disabled', true);
};
this.folder_size = function(folder)
{
var lock = this.set_busy(true, 'loading');
this.http_post('folder-size', '_mbox='+urlencode(folder), lock);
};
this.folder_size_update = function(size)
{
$('#folder-size').replaceWith(size);
};
/*********************************************************/
/********* GUI functionality *********/
/*********************************************************/
var init_button = function(cmd, prop)
{
var elm = document.getElementById(prop.id);
if (!elm)
return;
var preload = false;
if (prop.type == 'image') {
elm = elm.parentNode;
preload = true;
}
elm._command = cmd;
elm._id = prop.id;
if (prop.sel) {
elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); };
elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); };
if (preload)
new Image().src = prop.sel;
}
if (prop.over) {
elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); };
elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); };
if (preload)
new Image().src = prop.over;
}
};
// enable/disable buttons for page shifting
this.set_page_buttons = function()
{
this.enable_command('nextpage', 'lastpage', (this.env.pagecount > this.env.current_page));
this.enable_command('previouspage', 'firstpage', (this.env.current_page > 1));
};
// set event handlers on registered buttons
this.init_buttons = function()
{
for (var cmd in this.buttons) {
if (typeof cmd !== 'string')
continue;
for (var i=0; i< this.buttons[cmd].length; i++) {
init_button(cmd, this.buttons[cmd][i]);
}
}
// set active task button
this.set_button(this.task, 'sel');
};
// set button to a specific state
this.set_button = function(command, state)
{
var n, button, obj, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
obj = document.getElementById(button.id);
// get default/passive setting of the button
if (obj && button.type=='image' && !button.status) {
button.pas = obj._original_src ? obj._original_src : obj.src;
// respect PNG fix on IE browsers
if (obj.runtimeStyle && obj.runtimeStyle.filter && obj.runtimeStyle.filter.match(/src=['"]([^'"]+)['"]/))
button.pas = RegExp.$1;
}
else if (obj && !button.status)
button.pas = String(obj.className);
// set image according to button state
if (obj && button.type=='image' && button[state]) {
button.status = state;
obj.src = button[state];
}
// set class name according to button state
else if (obj && button[state] !== undefined) {
button.status = state;
obj.className = button[state];
}
// disable/enable input buttons
if (obj && button.type=='input') {
button.status = state;
obj.disabled = !state;
}
}
};
// display a specific alttext
this.set_alttext = function(command, label)
{
var n, button, obj, link, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
obj = document.getElementById(button.id);
if (button.type == 'image' && obj) {
obj.setAttribute('alt', this.get_label(label));
if ((link = obj.parentNode) && link.tagName.toLowerCase() == 'a')
link.setAttribute('title', this.get_label(label));
}
else if (obj)
obj.setAttribute('title', this.get_label(label));
}
};
// mouse over button
this.button_over = function(command, id)
{
var n, button, obj, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
if (button.id == id && button.status == 'act') {
obj = document.getElementById(button.id);
if (obj && button.over) {
if (button.type == 'image')
obj.src = button.over;
else
obj.className = button.over;
}
}
}
};
// mouse down on button
this.button_sel = function(command, id)
{
var n, button, obj, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
if (button.id == id && button.status == 'act') {
obj = document.getElementById(button.id);
if (obj && button.sel) {
if (button.type == 'image')
obj.src = button.sel;
else
obj.className = button.sel;
}
this.buttons_sel[id] = command;
}
}
};
// mouse out of button
this.button_out = function(command, id)
{
var n, button, obj, a_buttons = this.buttons[command],
len = a_buttons ? a_buttons.length : 0;
for (n=0; n<len; n++) {
button = a_buttons[n];
if (button.id == id && button.status == 'act') {
obj = document.getElementById(button.id);
if (obj && button.act) {
if (button.type == 'image')
obj.src = button.act;
else
obj.className = button.act;
}
}
}
};
this.focus_textfield = function(elem)
{
elem._hasfocus = true;
var $elem = $(elem);
if ($elem.hasClass('placeholder') || $elem.val() == elem._placeholder)
$elem.val('').removeClass('placeholder').attr('spellcheck', true);
};
this.blur_textfield = function(elem)
{
elem._hasfocus = false;
var $elem = $(elem);
if (elem._placeholder && (!$elem.val() || $elem.val() == elem._placeholder))
$elem.addClass('placeholder').attr('spellcheck', false).val(elem._placeholder);
};
// write to the document/window title
this.set_pagetitle = function(title)
{
if (title && document.title)
document.title = title;
};
// display a system message, list of types in common.css (below #message definition)
this.display_message = function(msg, type, timeout)
{
// pass command to parent window
if (this.is_framed())
return parent.rcmail.display_message(msg, type, timeout);
if (!this.gui_objects.message) {
// save message in order to display after page loaded
if (type != 'loading')
this.pending_message = [msg, type, timeout];
return false;
}
type = type ? type : 'notice';
var ref = this,
key = this.html_identifier(msg),
date = new Date(),
id = type + date.getTime();
if (!timeout)
timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1);
if (type == 'loading') {
key = 'loading';
timeout = this.env.request_timeout * 1000;
if (!msg)
msg = this.get_label('loading');
}
// The same message is already displayed
if (this.messages[key]) {
// replace label
if (this.messages[key].obj)
this.messages[key].obj.html(msg);
// store label in stack
if (type == 'loading') {
this.messages[key].labels.push({'id': id, 'msg': msg});
}
// add element and set timeout
this.messages[key].elements.push(id);
window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
return id;
}
// create DOM object and display it
var obj = $('<div>').addClass(type).html(msg).data('key', key),
cont = $(this.gui_objects.message).append(obj).show();
this.messages[key] = {'obj': obj, 'elements': [id]};
if (type == 'loading') {
this.messages[key].labels = [{'id': id, 'msg': msg}];
}
else {
obj.click(function() { return ref.hide_message(obj); });
}
if (timeout > 0)
window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
return id;
};
// make a message to disapear
this.hide_message = function(obj, fade)
{
// pass command to parent window
if (this.is_framed())
return parent.rcmail.hide_message(obj, fade);
var k, n, i, msg, m = this.messages;
// Hide message by object, don't use for 'loading'!
if (typeof obj === 'object') {
$(obj)[fade?'fadeOut':'hide']();
msg = $(obj).data('key');
if (this.messages[msg])
delete this.messages[msg];
}
// Hide message by id
else {
for (k in m) {
for (n in m[k].elements) {
if (m[k] && m[k].elements[n] == obj) {
m[k].elements.splice(n, 1);
// hide DOM element if last instance is removed
if (!m[k].elements.length) {
m[k].obj[fade?'fadeOut':'hide']();
delete m[k];
}
// set pending action label for 'loading' message
else if (k == 'loading') {
for (i in m[k].labels) {
if (m[k].labels[i].id == obj) {
delete m[k].labels[i];
}
else {
msg = m[k].labels[i].msg;
}
m[k].obj.html(msg);
}
}
}
}
}
}
};
// mark a mailbox as selected and set environment variable
this.select_folder = function(name, prefix, encode)
{
if (this.gui_objects.folderlist) {
var current_li, target_li;
if ((current_li = $('li.selected', this.gui_objects.folderlist))) {
current_li.removeClass('selected').addClass('unfocused');
}
if ((target_li = this.get_folder_li(name, prefix, encode))) {
$(target_li).removeClass('unfocused').addClass('selected');
}
// trigger event hook
this.triggerEvent('selectfolder', { folder:name, prefix:prefix });
}
};
// adds a class to selected folder
this.mark_folder = function(name, class_name, prefix, encode)
{
$(this.get_folder_li(name, prefix, encode)).addClass(class_name);
};
// adds a class to selected folder
this.unmark_folder = function(name, class_name, prefix, encode)
{
$(this.get_folder_li(name, prefix, encode)).removeClass(class_name);
};
// helper method to find a folder list item
this.get_folder_li = function(name, prefix, encode)
{
if (!prefix)
prefix = 'rcmli';
if (this.gui_objects.folderlist) {
name = this.html_identifier(name, encode);
return document.getElementById(prefix+name);
}
return null;
};
// for reordering column array (Konqueror workaround)
// and for setting some message list global variables
this.set_message_coltypes = function(coltypes, repl)
{
var list = this.message_list,
thead = list ? list.list.tHead : null,
cell, col, n, len, th, tr;
this.env.coltypes = coltypes;
// replace old column headers
if (thead) {
if (repl) {
th = document.createElement('thead');
tr = document.createElement('tr');
for (c=0, len=repl.length; c < len; c++) {
cell = document.createElement('td');
cell.innerHTML = repl[c].html;
if (repl[c].id) cell.id = repl[c].id;
if (repl[c].className) cell.className = repl[c].className;
tr.appendChild(cell);
}
th.appendChild(tr);
thead.parentNode.replaceChild(th, thead);
thead = th;
}
for (n=0, len=this.env.coltypes.length; n<len; n++) {
col = this.env.coltypes[n];
if ((cell = thead.rows[0].cells[n]) && (col=='from' || col=='to')) {
cell.id = 'rcm'+col;
// if we have links for sorting, it's a bit more complicated...
if (cell.firstChild && cell.firstChild.tagName.toLowerCase()=='a') {
cell = cell.firstChild;
cell.onclick = function(){ return rcmail.command('sort', this.__col, this); };
cell.__col = col;
}
cell.innerHTML = this.get_label(col);
}
}
}
this.env.subject_col = null;
this.env.flagged_col = null;
this.env.status_col = null;
if ((n = $.inArray('subject', this.env.coltypes)) >= 0) {
this.env.subject_col = n;
if (list)
list.subject_col = n;
}
if ((n = $.inArray('flag', this.env.coltypes)) >= 0)
this.env.flagged_col = n;
if ((n = $.inArray('status', this.env.coltypes)) >= 0)
this.env.status_col = n;
if (list)
list.init_header();
};
// replace content of row count display
this.set_rowcount = function(text, mbox)
{
// #1487752
if (mbox && mbox != this.env.mailbox)
return false;
$(this.gui_objects.countdisplay).html(text);
// update page navigation buttons
this.set_page_buttons();
};
// replace content of mailboxname display
this.set_mailboxname = function(content)
{
if (this.gui_objects.mailboxname && content)
this.gui_objects.mailboxname.innerHTML = content;
};
// replace content of quota display
this.set_quota = function(content)
{
if (content && this.gui_objects.quotadisplay) {
if (typeof content === 'object' && content.type == 'image')
this.percent_indicator(this.gui_objects.quotadisplay, content);
else
$(this.gui_objects.quotadisplay).html(content.percent+'%').attr('title', content.title);
}
this.triggerEvent('setquota', content);
};
// update the mailboxlist
this.set_unread_count = function(mbox, count, set_title, mark)
{
if (!this.gui_objects.mailboxlist)
return false;
this.env.unread_counts[mbox] = count;
this.set_unread_count_display(mbox, set_title);
if (mark)
this.mark_folder(mbox, mark, '', true);
else if (!count)
this.unmark_folder(mbox, 'recent', '', true);
};
// update the mailbox count display
this.set_unread_count_display = function(mbox, set_title)
{
var reg, link, text_obj, item, mycount, childcount, div;
if (item = this.get_folder_li(mbox, '', true)) {
mycount = this.env.unread_counts[mbox] ? this.env.unread_counts[mbox] : 0;
link = $(item).children('a').eq(0);
text_obj = link.children('span.unreadcount');
if (!text_obj.length && mycount)
text_obj = $('<span>').addClass('unreadcount').appendTo(link);
reg = /\s+\([0-9]+\)$/i;
childcount = 0;
if ((div = item.getElementsByTagName('div')[0]) &&
div.className.match(/collapsed/)) {
// add children's counters
for (var k in this.env.unread_counts)
if (k.indexOf(mbox + this.env.delimiter) == 0)
childcount += this.env.unread_counts[k];
}
if (mycount && text_obj.length)
text_obj.html(this.env.unreadwrap.replace(/%[sd]/, mycount));
else if (text_obj.length)
text_obj.remove();
// set parent's display
reg = new RegExp(RegExp.escape(this.env.delimiter) + '[^' + RegExp.escape(this.env.delimiter) + ']+$');
if (mbox.match(reg))
this.set_unread_count_display(mbox.replace(reg, ''), false);
// set the right classes
if ((mycount+childcount)>0)
$(item).addClass('unread');
else
$(item).removeClass('unread');
}
// set unread count to window title
reg = /^\([0-9]+\)\s+/i;
if (set_title && document.title) {
var new_title = '',
doc_title = String(document.title);
if (mycount && doc_title.match(reg))
new_title = doc_title.replace(reg, '('+mycount+') ');
else if (mycount)
new_title = '('+mycount+') '+doc_title;
else
new_title = doc_title.replace(reg, '');
this.set_pagetitle(new_title);
}
};
- this.toggle_prefer_html = function(checkbox)
- {
- $('#rcmfd_show_images').prop('disabled', !checkbox.checked).val(0);
- };
-
- this.toggle_preview_pane = function(checkbox)
- {
- $('#rcmfd_preview_pane_mark_read').prop('disabled', !checkbox.checked);
- };
-
// display fetched raw headers
this.set_headers = function(content)
{
if (this.gui_objects.all_headers_row && this.gui_objects.all_headers_box && content)
$(this.gui_objects.all_headers_box).html(content).show();
};
// display all-headers row and fetch raw message headers
this.load_headers = function(elem)
{
if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box || !this.env.uid)
return;
$(elem).removeClass('show-headers').addClass('hide-headers');
$(this.gui_objects.all_headers_row).show();
elem.onclick = function() { rcmail.hide_headers(elem); };
// fetch headers only once
if (!this.gui_objects.all_headers_box.innerHTML) {
var lock = this.display_message(this.get_label('loading'), 'loading');
this.http_post('headers', '_uid='+this.env.uid, lock);
}
};
// hide all-headers row
this.hide_headers = function(elem)
{
if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box)
return;
$(elem).removeClass('hide-headers').addClass('show-headers');
$(this.gui_objects.all_headers_row).hide();
elem.onclick = function() { rcmail.load_headers(elem); };
};
// percent (quota) indicator
this.percent_indicator = function(obj, data)
{
if (!data || !obj)
return false;
var limit_high = 80,
limit_mid = 55,
width = data.width ? data.width : this.env.indicator_width ? this.env.indicator_width : 100,
height = data.height ? data.height : this.env.indicator_height ? this.env.indicator_height : 14,
quota = data.percent ? Math.abs(parseInt(data.percent)) : 0,
quota_width = parseInt(quota / 100 * width),
pos = $(obj).position();
// workarounds for Opera and Webkit bugs
pos.top = Math.max(0, pos.top);
pos.left = Math.max(0, pos.left);
this.env.indicator_width = width;
this.env.indicator_height = height;
// overlimit
if (quota_width > width) {
quota_width = width;
quota = 100;
}
if (data.title)
data.title = this.get_label('quota') + ': ' + data.title;
// main div
var main = $('<div>');
main.css({position: 'absolute', top: pos.top, left: pos.left,
width: width + 'px', height: height + 'px', zIndex: 100, lineHeight: height + 'px'})
.attr('title', data.title).addClass('quota_text').html(quota + '%');
// used bar
var bar1 = $('<div>');
bar1.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
width: quota_width + 'px', height: height + 'px', zIndex: 99});
// background
var bar2 = $('<div>');
bar2.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
width: width + 'px', height: height + 'px', zIndex: 98})
.addClass('quota_bg');
if (quota >= limit_high) {
main.addClass(' quota_text_high');
bar1.addClass('quota_high');
}
else if(quota >= limit_mid) {
main.addClass(' quota_text_mid');
bar1.addClass('quota_mid');
}
else {
main.addClass(' quota_text_low');
bar1.addClass('quota_low');
}
// replace quota image
$(obj).html('').append(bar1).append(bar2).append(main);
// update #quotaimg title
$('#quotaimg').attr('title', data.title);
};
/********************************************************/
/********* html to text conversion functions *********/
/********************************************************/
this.html2plain = function(htmlText, id)
{
var rcmail = this,
url = '?_task=utils&_action=html2text',
lock = this.set_busy(true, 'converting');
this.log('HTTP POST: ' + url);
$.ajax({ type: 'POST', url: url, data: htmlText, contentType: 'application/octet-stream',
error: function(o, status, err) { rcmail.http_error(o, status, err, lock); },
success: function(data) { rcmail.set_busy(false, null, lock); $('#'+id).val(data); rcmail.log(data); }
});
};
this.plain2html = function(plain, id)
{
var lock = this.set_busy(true, 'converting');
plain = plain.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
$('#'+id).val(plain ? '<pre>'+plain+'</pre>' : '');
this.set_busy(false, null, lock);
};
/********************************************************/
/********* remote request methods *********/
/********************************************************/
// compose a valid url with the given parameters
this.url = function(action, query)
{
var querystring = typeof query === 'string' ? '&' + query : '';
if (typeof action !== 'string')
query = action;
else if (!query || typeof query !== 'object')
query = {};
if (action)
query._action = action;
else
query._action = this.env.action;
var base = this.env.comm_path;
// overwrite task name
if (query._action.match(/([a-z]+)\/([a-z0-9-_.]+)/)) {
query._action = RegExp.$2;
base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
}
// remove undefined values
var param = {};
for (var k in query) {
if (query[k] !== undefined && query[k] !== null)
param[k] = query[k];
}
return base + '&' + $.param(param) + querystring;
};
this.redirect = function(url, lock)
{
if (lock || lock === null)
this.set_busy(true);
if (this.is_framed())
parent.rcmail.redirect(url, lock);
else
this.location_href(url, window);
};
this.goto_url = function(action, query, lock)
{
this.redirect(this.url(action, query));
};
this.location_href = function(url, target, frame)
{
if (frame)
this.lock_frame();
// simulate real link click to force IE to send referer header
if (bw.ie && target == window)
$('<a>').attr('href', url).appendTo(document.body).get(0).click();
else
target.location.href = url;
};
// send a http request to the server
this.http_request = function(action, query, lock)
{
var url = this.url(action, query);
// trigger plugin hook
var result = this.triggerEvent('request'+action, query);
if (result !== undefined) {
// abort if one the handlers returned false
if (result === false)
return false;
else
query = result;
}
url += '&_remote=1';
// send request
this.log('HTTP GET: ' + url);
return $.ajax({
type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json',
success: function(data){ ref.http_response(data); },
error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }
});
};
// send a http POST request to the server
this.http_post = function(action, postdata, lock)
{
var url = this.url(action);
if (postdata && typeof postdata === 'object') {
postdata._remote = 1;
postdata._unlock = (lock ? lock : 0);
}
else
postdata += (postdata ? '&' : '') + '_remote=1' + (lock ? '&_unlock='+lock : '');
// trigger plugin hook
var result = this.triggerEvent('request'+action, postdata);
if (result !== undefined) {
// abort if one the handlers returned false
if (result === false)
return false;
else
postdata = result;
}
// send request
this.log('HTTP POST: ' + url);
return $.ajax({
type: 'POST', url: url, data: postdata, dataType: 'json',
success: function(data){ ref.http_response(data); },
error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }
});
};
// aborts ajax request
this.abort_request = function(r)
{
if (r.request)
r.request.abort();
if (r.lock)
this.set_busy(false, null, r.lock);
};
// handle HTTP response
this.http_response = function(response)
{
if (!response)
return;
if (response.unlock)
this.set_busy(false);
this.triggerEvent('responsebefore', {response: response});
this.triggerEvent('responsebefore'+response.action, {response: response});
// set env vars
if (response.env)
this.set_env(response.env);
// we have labels to add
if (typeof response.texts === 'object') {
for (var name in response.texts)
if (typeof response.texts[name] === 'string')
this.add_label(name, response.texts[name]);
}
// if we get javascript code from server -> execute it
if (response.exec) {
this.log(response.exec);
eval(response.exec);
}
// execute callback functions of plugins
if (response.callbacks && response.callbacks.length) {
for (var i=0; i < response.callbacks.length; i++)
this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]);
}
// process the response data according to the sent action
switch (response.action) {
case 'delete':
if (this.task == 'addressbook') {
var sid, uid = this.contact_list.get_selection(), writable = false;
if (uid && this.contact_list.rows[uid]) {
// search results, get source ID from record ID
if (this.env.source == '') {
sid = String(uid).replace(/^[^-]+-/, '');
writable = sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly;
}
else {
writable = !this.env.address_sources[this.env.source].readonly;
}
}
this.enable_command('compose', (uid && this.contact_list.rows[uid]));
this.enable_command('delete', 'edit', writable);
this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
}
case 'moveto':
if (this.env.action == 'show') {
// re-enable commands on move/delete error
this.enable_command(this.env.message_commands, true);
if (!this.env.list_post)
this.enable_command('reply-list', false);
}
else if (this.task == 'addressbook') {
this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
}
case 'purge':
case 'expunge':
if (this.task == 'mail') {
if (!this.env.messagecount) {
// clear preview pane content
if (this.env.contentframe)
this.show_contentframe(false);
// disable commands useless when mailbox is empty
this.enable_command(this.env.message_commands, 'purge', 'expunge',
'select-all', 'select-none', 'sort', 'expand-all', 'expand-unread', 'collapse-all', false);
}
if (this.message_list)
this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
}
break;
case 'check-recent':
case 'getunread':
case 'search':
this.env.qsearch = null;
case 'list':
if (this.task == 'mail') {
this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
this.enable_command('purge', this.purge_mailbox_test());
this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount);
if (response.action == 'list' || response.action == 'search') {
this.msglist_select(this.message_list);
this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
}
}
else if (this.task == 'addressbook') {
this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
if (response.action == 'list' || response.action == 'search') {
this.enable_command('search-create', this.env.source == '');
this.enable_command('search-delete', this.env.search_id);
this.update_group_commands();
this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
}
}
break;
}
if (response.unlock)
this.hide_message(response.unlock);
this.triggerEvent('responseafter', {response: response});
this.triggerEvent('responseafter'+response.action, {response: response});
};
// handle HTTP request errors
this.http_error = function(request, status, err, lock)
{
var errmsg = request.statusText;
this.set_busy(false, null, lock);
request.abort();
if (request.status && errmsg)
this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
};
// post the given form to a hidden iframe
this.async_upload_form = function(form, action, onload)
{
var ts = new Date().getTime(),
frame_name = 'rcmupload'+ts;
// upload progress support
if (this.env.upload_progress_name) {
var fname = this.env.upload_progress_name,
field = $('input[name='+fname+']', form);
if (!field.length) {
field = $('<input>').attr({type: 'hidden', name: fname});
field.prependTo(form);
}
field.val(ts);
}
// have to do it this way for IE
// otherwise the form will be posted to a new window
if (document.all) {
var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
document.body.insertAdjacentHTML('BeforeEnd', html);
}
else { // for standards-compilant browsers
var frame = document.createElement('iframe');
frame.name = frame_name;
frame.style.border = 'none';
frame.style.width = 0;
frame.style.height = 0;
frame.style.visibility = 'hidden';
document.body.appendChild(frame);
}
// handle upload errors, parsing iframe content in onload
$(frame_name).bind('load', {ts:ts}, onload);
$(form).attr({
target: frame_name,
action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }),
method: 'POST'})
.attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
.submit();
return frame_name;
};
// starts interval for keep-alive/check-recent signal
this.start_keepalive = function()
{
if (this._int)
clearInterval(this._int);
if (this.env.keep_alive && !this.env.framed && this.task == 'mail' && this.gui_objects.mailboxlist)
this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000);
else if (this.env.keep_alive && !this.env.framed && this.task != 'login' && this.env.action != 'print')
this._int = setInterval(function(){ ref.keep_alive(); }, this.env.keep_alive * 1000);
};
// sends keep-alive signal
this.keep_alive = function()
{
if (!this.busy)
this.http_request('keep-alive');
};
// sends request to check for recent messages
this.check_for_recent = function(refresh)
{
if (this.busy)
return;
var lock, addurl = '_mbox=' + urlencode(this.env.mailbox);
if (refresh) {
lock = this.set_busy(true, 'checkingmail');
addurl += '&_refresh=1';
// reset check-recent interval
this.start_keepalive();
}
if (this.gui_objects.messagelist)
addurl += '&_list=1';
if (this.gui_objects.quotadisplay)
addurl += '&_quota=1';
if (this.env.search_request)
addurl += '&_search=' + this.env.search_request;
this.http_request('check-recent', addurl, lock);
};
/********************************************************/
/********* helper methods *********/
/********************************************************/
// check if we're in show mode or if we have a unique selection
// and return the message uid
this.get_single_uid = function()
{
return this.env.uid ? this.env.uid : (this.message_list ? this.message_list.get_single_selection() : null);
};
// same as above but for contacts
this.get_single_cid = function()
{
return this.env.cid ? this.env.cid : (this.contact_list ? this.contact_list.get_single_selection() : null);
};
// gets cursor position
this.get_caret_pos = function(obj)
{
if (obj.selectionEnd !== undefined)
return obj.selectionEnd;
else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
if (range.parentElement()!=obj)
return 0;
var gm = range.duplicate();
if (obj.tagName == 'TEXTAREA')
gm.moveToElementText(obj);
else
gm.expand('textedit');
gm.setEndPoint('EndToStart', range);
var p = gm.text.length;
return p<=obj.value.length ? p : -1;
}
else
return obj.value.length;
};
// moves cursor to specified position
this.set_caret_pos = function(obj, pos)
{
if (obj.setSelectionRange)
obj.setSelectionRange(pos, pos);
else if (obj.createTextRange) {
var range = obj.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
};
// disable/enable all fields of a form
this.lock_form = function(form, lock)
{
if (!form || !form.elements)
return;
var n, len, elm;
if (lock)
this.disabled_form_elements = [];
for (n=0, len=form.elements.length; n<len; n++) {
elm = form.elements[n];
if (elm.type == 'hidden')
continue;
// remember which elem was disabled before lock
if (lock && elm.disabled)
this.disabled_form_elements.push(elm);
// check this.disabled_form_elements before inArray() as a workaround for FF5 bug
// http://bugs.jquery.com/ticket/9873
else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0))
elm.disabled = lock;
}
};
} // end object rcube_webmail
// some static methods
rcube_webmail.long_subject_title = function(elem, indent)
{
if (!elem.title) {
var $elem = $(elem);
if ($elem.width() + indent * 15 > $elem.parent().width())
elem.title = $elem.html();
}
};
rcube_webmail.long_subject_title_ie = function(elem, indent)
{
if (!elem.title) {
var $elem = $(elem),
txt = $.trim($elem.text()),
tmp = $('<span>').text(txt)
.css({'position': 'absolute', 'float': 'left', 'visibility': 'hidden',
'font-size': $elem.css('font-size'), 'font-weight': $elem.css('font-weight')})
.appendTo($('body')),
w = tmp.width();
tmp.remove();
if (w + indent * 15 > $elem.width())
elem.title = txt;
}
};
// copy event engine prototype
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index f192209ed..3efff4a2d 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -1,500 +1,499 @@
<?php
/*
+-----------------------------------------------------------------------+
| language/en_US/labels.inc |
| |
| Language file of the Roundcube Webmail client |
| Copyright (C) 2005-2010, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
@version $Id$
*/
$labels = array();
// login page
$labels['welcome'] = 'Welcome to $product';
$labels['username'] = 'Username';
$labels['password'] = 'Password';
$labels['server'] = 'Server';
$labels['login'] = 'Login';
// taskbar
$labels['logout'] = 'Logout';
$labels['mail'] = 'Mail';
$labels['settings'] = 'Settings';
$labels['addressbook'] = 'Address Book';
// mailbox names
$labels['inbox'] = 'Inbox';
$labels['drafts'] = 'Drafts';
$labels['sent'] = 'Sent';
$labels['trash'] = 'Trash';
$labels['junk'] = 'Junk';
// message listing
$labels['subject'] = 'Subject';
$labels['from'] = 'From';
$labels['to'] = 'To';
$labels['cc'] = 'Cc';
$labels['bcc'] = 'Bcc';
$labels['replyto'] = 'Reply-To';
$labels['followupto'] = 'Followup-To';
$labels['date'] = 'Date';
$labels['size'] = 'Size';
$labels['priority'] = 'Priority';
$labels['organization'] = 'Organization';
$labels['readstatus'] = 'Read status';
$labels['mailboxlist'] = 'Folders';
$labels['messagesfromto'] = 'Messages $from to $to of $count';
$labels['threadsfromto'] = 'Threads $from to $to of $count';
$labels['messagenrof'] = 'Message $nr of $count';
$labels['copy'] = 'Copy';
$labels['move'] = 'Move';
$labels['moveto'] = 'Move to...';
$labels['download'] = 'Download';
$labels['filename'] = 'File name';
$labels['filesize'] = 'File size';
$labels['addtoaddressbook'] = 'Add to address book';
// weekdays short
$labels['sun'] = 'Sun';
$labels['mon'] = 'Mon';
$labels['tue'] = 'Tue';
$labels['wed'] = 'Wed';
$labels['thu'] = 'Thu';
$labels['fri'] = 'Fri';
$labels['sat'] = 'Sat';
// weekdays long
$labels['sunday'] = 'Sunday';
$labels['monday'] = 'Monday';
$labels['tuesday'] = 'Tuesday';
$labels['wednesday'] = 'Wednesday';
$labels['thursday'] = 'Thursday';
$labels['friday'] = 'Friday';
$labels['saturday'] = 'Saturday';
// months short
$labels['jan'] = 'Jan';
$labels['feb'] = 'Feb';
$labels['mar'] = 'Mar';
$labels['apr'] = 'Apr';
$labels['may'] = 'May';
$labels['jun'] = 'Jun';
$labels['jul'] = 'Jul';
$labels['aug'] = 'Aug';
$labels['sep'] = 'Sep';
$labels['oct'] = 'Oct';
$labels['nov'] = 'Nov';
$labels['dec'] = 'Dec';
// months long
$labels['longjan'] = 'January';
$labels['longfeb'] = 'February';
$labels['longmar'] = 'March';
$labels['longapr'] = 'April';
$labels['longmay'] = 'May';
$labels['longjun'] = 'June';
$labels['longjul'] = 'July';
$labels['longaug'] = 'August';
$labels['longsep'] = 'September';
$labels['longoct'] = 'October';
$labels['longnov'] = 'November';
$labels['longdec'] = 'December';
$labels['today'] = 'Today';
// toolbar buttons
$labels['checkmail'] = 'Check for new messages';
$labels['writenewmessage'] = 'Create a new message';
$labels['replytomessage'] = 'Reply to sender';
$labels['replytoallmessage'] = 'Reply to list or to sender and all recipients';
$labels['replyall'] = 'Reply all';
$labels['replylist'] = 'Reply list';
$labels['forwardinline'] = 'Forward inline';
$labels['forwardattachment'] = 'Forward as attachment';
$labels['forwardmessage'] = 'Forward the message';
$labels['deletemessage'] = 'Delete message';
$labels['movemessagetotrash'] = 'Move message to trash';
$labels['printmessage'] = 'Print this message';
$labels['previousmessage'] = 'Show previous message';
$labels['firstmessage'] = 'Show first message';
$labels['nextmessage'] = 'Show next message';
$labels['lastmessage'] = 'Show last message';
$labels['backtolist'] = 'Back to message list';
$labels['viewsource'] = 'Show source';
$labels['markmessages'] = 'Mark messages';
$labels['markread'] = 'As read';
$labels['markunread'] = 'As unread';
$labels['markflagged'] = 'As flagged';
$labels['markunflagged'] = 'As unflagged';
$labels['moreactions'] = 'More actions...';
$labels['select'] = 'Select';
$labels['all'] = 'All';
$labels['none'] = 'None';
$labels['currpage'] = 'Current page';
$labels['unread'] = 'Unread';
$labels['flagged'] = 'Flagged';
$labels['unanswered'] = 'Unanswered';
$labels['deleted'] = 'Deleted';
$labels['invert'] = 'Invert';
$labels['filter'] = 'Filter';
$labels['list'] = 'List';
$labels['threads'] = 'Threads';
$labels['expand-all'] = 'Expand All';
$labels['expand-unread'] = 'Expand Unread';
$labels['collapse-all'] = 'Collapse All';
$labels['threaded'] = 'Threaded';
$labels['autoexpand_threads'] = 'Expand message threads';
$labels['do_expand'] = 'all threads';
$labels['expand_only_unread'] = 'only with unread messages';
$labels['fromto'] = 'From/To';
$labels['flag'] = 'Flag';
$labels['attachment'] = 'Attachment';
$labels['nonesort'] = 'None';
$labels['sentdate'] = 'Sent date';
$labels['arrival'] = 'Arrival date';
$labels['asc'] = 'ascending';
$labels['desc'] = 'descending';
$labels['listcolumns'] = 'List columns';
$labels['listsorting'] = 'Sorting column';
$labels['listorder'] = 'Sorting order';
$labels['listmode'] = 'List view mode';
$labels['folderactions'] = 'Folder actions...';
$labels['compact'] = 'Compact';
$labels['empty'] = 'Empty';
$labels['quota'] = 'Disk usage';
$labels['unknown'] = 'unknown';
$labels['unlimited'] = 'unlimited';
$labels['quicksearch'] = 'Quick search';
$labels['resetsearch'] = 'Reset search';
$labels['searchmod'] = 'Search modifiers';
$labels['msgtext'] = 'Entire message';
$labels['openinextwin'] = 'Open in new window';
$labels['emlsave'] = 'Download (.eml)';
// message compose
$labels['compose'] = 'Compose a message';
$labels['editasnew'] = 'Edit as new';
$labels['savemessage'] = 'Save this draft';
$labels['sendmessage'] = 'Send now';
$labels['addattachment'] = 'Attach a file';
$labels['charset'] = 'Charset';
$labels['editortype'] = 'Editor type';
$labels['returnreceipt'] = 'Return receipt';
$labels['dsn'] = 'Delivery status notification';
$labels['mailreplyintro'] = 'On $date, $sender wrote:';
$labels['originalmessage'] = 'Original Message';
$labels['editidents'] = 'Edit identities';
$labels['checkspelling'] = 'Check spelling';
$labels['resumeediting'] = 'Resume editing';
$labels['revertto'] = 'Revert to';
$labels['attachments'] = 'Attachments';
$labels['upload'] = 'Upload';
$labels['uploadprogress'] = '$percent ($current from $total)';
$labels['close'] = 'Close';
$labels['messageoptions'] = 'Message options...';
$labels['low'] = 'Low';
$labels['lowest'] = 'Lowest';
$labels['normal'] = 'Normal';
$labels['high'] = 'High';
$labels['highest'] = 'Highest';
$labels['nosubject'] = '(no subject)';
$labels['showimages'] = 'Display images';
$labels['alwaysshow'] = 'Always show images from $sender';
$labels['isdraft'] = 'This is a draft message.';
$labels['htmltoggle'] = 'HTML';
$labels['plaintoggle'] = 'Plain text';
$labels['savesentmessagein'] = 'Save sent message in';
$labels['dontsave'] = 'don\'t save';
$labels['maxuploadsize'] = 'Maximum allowed file size is $size';
$labels['addcc'] = 'Add Cc';
$labels['addbcc'] = 'Add Bcc';
$labels['addreplyto'] = 'Add Reply-To';
$labels['addfollowupto'] = 'Add Followup-To';
// mdn
$labels['mdnrequest'] = 'The sender of this message has asked to be notified when you read this message. Do you wish to notify the sender?';
$labels['receiptread'] = 'Return Receipt (read)';
$labels['yourmessage'] = 'This is a Return Receipt for your message';
$labels['receiptnote'] = 'Note: This receipt only acknowledges that the message was displayed on the recipient\'s computer. There is no guarantee that the recipient has read or understood the message contents.';
// address boook
$labels['name'] = 'Display Name';
$labels['firstname'] = 'First Name';
$labels['surname'] = 'Last Name';
$labels['middlename'] = 'Middle Name';
$labels['nameprefix'] = 'Prefix';
$labels['namesuffix'] = 'Suffix';
$labels['nickname'] = 'Nickname';
$labels['jobtitle'] = 'Job Title';
$labels['organization'] = 'Company';
$labels['department'] = 'Department';
$labels['gender'] = 'Gender';
$labels['maidenname'] = 'Maiden Name';
$labels['email'] = 'Email';
$labels['phone'] = 'Phone';
$labels['address'] = 'Address';
$labels['street'] = 'Street';
$labels['locality'] = 'City';
$labels['zipcode'] = 'ZIP Code';
$labels['region'] = 'State/Province';
$labels['country'] = 'Country';
$labels['birthday'] = 'Birthday';
$labels['anniversary'] = 'Anniversary';
$labels['website'] = 'Website';
$labels['instantmessenger'] = 'IM';
$labels['notes'] = 'Notes';
$labels['male'] = 'male';
$labels['female'] = 'female';
$labels['manager'] = 'Manager';
$labels['assistant'] = 'Assistant';
$labels['spouse'] = 'Spouse';
$labels['allfields'] = 'All fields';
$labels['search'] = 'Search';
$labels['advsearch'] = 'Advanced Search';
$labels['other'] = 'Other';
$labels['typehome'] = 'Home';
$labels['typework'] = 'Work';
$labels['typeother'] = 'Other';
$labels['typemobile'] = 'Mobile';
$labels['typemain'] = 'Main';
$labels['typehomefax'] = 'Home Fax';
$labels['typeworkfax'] = 'Work Fax';
$labels['typecar'] = 'Car';
$labels['typepager'] = 'Pager';
$labels['typevideo'] = 'Video';
$labels['typeassistant'] = 'Assistant';
$labels['typehomepage'] = 'Home Page';
$labels['typeblog'] = 'Blog';
$labels['typeprofile'] = 'Profile';
$labels['addfield'] = 'Add field...';
$labels['addcontact'] = 'Add new contact';
$labels['editcontact'] = 'Edit contact';
$labels['contacts'] = 'Contacts';
$labels['contactproperties'] = 'Contact properties';
$labels['personalinfo'] = 'Personal information';
$labels['edit'] = 'Edit';
$labels['cancel'] = 'Cancel';
$labels['save'] = 'Save';
$labels['delete'] = 'Delete';
$labels['rename'] = 'Rename';
$labels['addphoto'] = 'Add';
$labels['replacephoto'] = 'Replace';
$labels['newcontact'] = 'Create new contact card';
$labels['deletecontact'] = 'Delete selected contacts';
$labels['composeto'] = 'Compose mail to';
$labels['contactsfromto'] = 'Contacts $from to $to of $count';
$labels['print'] = 'Print';
$labels['export'] = 'Export';
$labels['exportvcards'] = 'Export contacts in vCard format';
$labels['newcontactgroup'] = 'Create new contact group';
$labels['grouprename'] = 'Rename group';
$labels['groupdelete'] = 'Delete group';
$labels['previouspage'] = 'Show previous page';
$labels['firstpage'] = 'Show first page';
$labels['nextpage'] = 'Show next page';
$labels['lastpage'] = 'Show last page';
$labels['group'] = 'Group';
$labels['groups'] = 'Groups';
$labels['personaladrbook'] = 'Personal Addresses';
$labels['searchsave'] = 'Save search';
$labels['searchdelete'] = 'Delete search';
$labels['import'] = 'Import';
$labels['importcontacts'] = 'Import contacts';
$labels['importfromfile'] = 'Import from file:';
$labels['importtarget'] = 'Add new contacts to address book:';
$labels['importreplace'] = 'Replace the entire address book';
$labels['importtext'] = 'You can upload contacts from an existing address book.<br/>We currently support importing addresses from the <a href="http://en.wikipedia.org/wiki/VCard">vCard</a> data format.';
$labels['done'] = 'Done';
// settings
$labels['settingsfor'] = 'Settings for';
$labels['about'] = 'About';
$labels['preferences'] = 'Preferences';
$labels['userpreferences'] = 'User preferences';
$labels['editpreferences'] = 'Edit user preferences';
$labels['identities'] = 'Identities';
$labels['manageidentities'] = 'Manage identities for this account';
$labels['newidentity'] = 'New identity';
$labels['newitem'] = 'New item';
$labels['edititem'] = 'Edit item';
$labels['preferhtml'] = 'Display HTML';
$labels['defaultcharset'] = 'Default Character Set';
$labels['htmlmessage'] = 'HTML Message';
$labels['dateformat'] = 'Date format';
$labels['timeformat'] = 'Time format';
$labels['prettydate'] = 'Pretty dates';
$labels['setdefault'] = 'Set default';
$labels['autodetect'] = 'Auto';
$labels['language'] = 'Language';
$labels['timezone'] = 'Time zone';
$labels['pagesize'] = 'Rows per page';
$labels['signature'] = 'Signature';
$labels['dstactive'] = 'Daylight saving time';
$labels['htmleditor'] = 'Compose HTML messages';
$labels['htmlonreply'] = 'on reply to HTML message only';
$labels['htmlsignature'] = 'HTML signature';
$labels['previewpane'] = 'Show preview pane';
$labels['skin'] = 'Interface skin';
$labels['logoutclear'] = 'Clear Trash on logout';
$labels['logoutcompact'] = 'Compact Inbox on logout';
$labels['uisettings'] = 'User Interface';
$labels['serversettings'] = 'Server Settings';
$labels['mailboxview'] = 'Mailbox View';
$labels['mdnrequests'] = 'On request for return receipt';
$labels['askuser'] = 'ask me';
$labels['autosend'] = 'send receipt';
$labels['autosendknown'] = 'send receipt to my contacts, otherwise ask me';
$labels['autosendknownignore'] = 'send receipt to my contacts, otherwise ignore';
$labels['ignore'] = 'ignore';
$labels['readwhendeleted'] = 'Mark the message as read on delete';
$labels['flagfordeletion'] = 'Flag the message for deletion instead of delete';
$labels['skipdeleted'] = 'Do not show deleted messages';
$labels['deletealways'] = 'If moving messages to Trash fails, delete them';
$labels['showremoteimages'] = 'Display remote inline images';
$labels['fromknownsenders'] = 'from known senders';
$labels['always'] = 'always';
$labels['showinlineimages'] = 'Display attached images below the message';
$labels['autosavedraft'] = 'Automatically save draft';
$labels['everynminutes'] = 'every $n minute(s)';
$labels['keepalive'] = 'Check for new messages on';
$labels['never'] = 'never';
$labels['immediately'] = 'immediately';
$labels['messagesdisplaying'] = 'Displaying Messages';
$labels['messagescomposition'] = 'Composing Messages';
$labels['mimeparamfolding'] = 'Attachment names';
$labels['2231folding'] = 'Full RFC 2231 (Thunderbird)';
$labels['miscfolding'] = 'RFC 2047/2231 (MS Outlook)';
$labels['2047folding'] = 'Full RFC 2047 (other)';
$labels['force7bit'] = 'Use MIME encoding for 8-bit characters';
$labels['advancedoptions'] = 'Advanced options';
$labels['focusonnewmessage'] = 'Focus browser window on new message';
$labels['checkallfolders'] = 'Check all folders for new messages';
$labels['displaynext'] = 'After message delete/move display the next message';
$labels['defaultfont'] = 'Default font of HTML message';
$labels['mainoptions'] = 'Main Options';
$labels['section'] = 'Section';
$labels['maintenance'] = 'Maintenance';
$labels['newmessage'] = 'New Message';
-$labels['listoptions'] = 'List Options';
$labels['signatureoptions'] = 'Signature Options';
$labels['whenreplying'] = 'When replying';
$labels['replytopposting'] = 'start new message above original';
$labels['replybottomposting'] = 'start new message below original';
$labels['replyremovesignature'] = 'When replying remove original signature from message';
$labels['autoaddsignature'] = 'Automatically add signature';
$labels['newmessageonly'] = 'new message only';
$labels['replyandforwardonly'] = 'replies and forwards only';
$labels['replysignaturepos'] = 'When replying or forwarding place signature';
$labels['belowquote'] = 'below the quote';
$labels['abovequote'] = 'above the quote';
$labels['insertsignature'] = 'Insert signature';
$labels['previewpanemarkread'] = 'Mark previewed messages as read';
$labels['afternseconds'] = 'after $n seconds';
$labels['reqmdn'] = 'Always request a return receipt';
$labels['reqdsn'] = 'Always request a delivery status notification';
$labels['replysamefolder'] = 'Place replies in the folder of the message being replied to';
$labels['defaultaddressbook'] = 'Add new contacts to the selected addressbook';
$labels['autocompletesingle'] = 'Skip alternative email addresses in autocompletion';
$labels['spellcheckbeforesend'] = 'Check spelling before sending a message';
$labels['spellcheckoptions'] = 'Spellcheck Options';
$labels['spellcheckignoresyms'] = 'Ignore words with symbols';
$labels['spellcheckignorenums'] = 'Ignore words with numbers';
$labels['spellcheckignorecaps'] = 'Ignore words with all letters capitalized';
$labels['addtodict'] = 'Add to dictionary';
$labels['folder'] = 'Folder';
$labels['folders'] = 'Folders';
$labels['foldername'] = 'Folder name';
$labels['subscribed'] = 'Subscribed';
$labels['messagecount'] = 'Messages';
$labels['create'] = 'Create';
$labels['createfolder'] = 'Create new folder';
$labels['managefolders'] = 'Manage folders';
$labels['specialfolders'] = 'Special Folders';
$labels['properties'] = 'Properties';
$labels['folderproperties'] = 'Folder properties';
$labels['parentfolder'] = 'Parent folder';
$labels['location'] = 'Location';
$labels['info'] = 'Information';
$labels['getfoldersize'] = 'Click to get folder size';
$labels['changesubscription'] = 'Click to change subscription';
$labels['foldertype'] = 'Folder Type';
$labels['personalfolder'] = 'Private Folder';
$labels['otherfolder'] = 'Other User\'s Folder';
$labels['sharedfolder'] = 'Public Folder';
$labels['sortby'] = 'Sort by';
$labels['sortasc'] = 'Sort ascending';
$labels['sortdesc'] = 'Sort descending';
$labels['undo'] = 'Undo';
$labels['plugin'] = 'Plugin';
$labels['version'] = 'Version';
$labels['source'] = 'Source';
$labels['license'] = 'License';
$labels['support'] = 'Get support';
// units
$labels['B'] = 'B';
$labels['KB'] = 'KB';
$labels['MB'] = 'MB';
$labels['GB'] = 'GB';
// character sets
$labels['unicode'] = 'Unicode';
$labels['english'] = 'English';
$labels['westerneuropean'] = 'Western European';
$labels['easterneuropean'] = 'Eastern European';
$labels['southeasterneuropean'] = 'South-Eastern European';
$labels['baltic'] = 'Baltic';
$labels['cyrillic'] = 'Cyrillic';
$labels['arabic'] = 'Arabic';
$labels['greek'] = 'Greek';
$labels['hebrew'] = 'Hebrew';
$labels['turkish'] = 'Turkish';
$labels['nordic'] = 'Nordic';
$labels['thai'] = 'Thai';
$labels['celtic'] = 'Celtic';
$labels['vietnamese'] = 'Vietnamese';
$labels['japanese'] = 'Japanese';
$labels['korean'] = 'Korean';
$labels['chinese'] = 'Chinese';
?>
diff --git a/program/steps/addressbook/delete.inc b/program/steps/addressbook/delete.inc
index 222535cf3..30e0ca13b 100644
--- a/program/steps/addressbook/delete.inc
+++ b/program/steps/addressbook/delete.inc
@@ -1,163 +1,163 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/addressbook/delete.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2009, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Delete the submitted contacts (CIDs) from the users address book |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
// process ajax requests only
if (!$OUTPUT->ajax_call)
return;
$cids = rcmail_get_cids();
$delcnt = 0;
// remove previous deletes
$undo_time = $RCMAIL->config->get('undo_timeout', 0);
$RCMAIL->session->remove('contact_undo');
foreach ($cids as $source => $cid)
{
$CONTACTS = rcmail_contact_source($source);
if ($CONTACTS->readonly) {
// more sources? do nothing, probably we have search results from
// more than one source, some of these sources can be readonly
if (count($cids) == 1) {
$OUTPUT->show_message('contactdelerror', 'error');
$OUTPUT->command('list_contacts');
$OUTPUT->send();
}
continue;
}
$plugin = $RCMAIL->plugins->exec_hook('contact_delete', array(
'id' => $cid, 'source' => $source));
$deleted = !$plugin['abort'] ? $CONTACTS->delete($cid, $undo_time < 1) : $plugin['result'];
if (!$deleted) {
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'contactdelerror', 'error');
$OUTPUT->command('list_contacts');
$OUTPUT->send();
}
else {
$delcnt += $deleted;
// store deleted contacts IDs in session for undo action
if ($undo_time > 0 && $CONTACTS->undelete) {
$_SESSION['contact_undo']['data'][$source] = $cid;
}
}
}
$page = isset($_SESSION['page']) ? $_SESSION['page'] : 1;
// update saved search after data changed
if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) {
$search = (array)$_SESSION['search'][$search_request];
$records = array();
// Get records from all sources (refresh search)
foreach ($search as $s => $set) {
$source = $RCMAIL->get_address_book($s);
// reset page
$source->set_page(1);
$source->set_pagesize(9999);
$source->set_search_set($set);
// get records
$result = $source->list_records(array('name', 'email'));
if (!$result->count) {
unset($search[$s]);
continue;
}
while ($row = $result->next()) {
$row['sourceid'] = $s;
$key = $row['name'] . ':' . $row['sourceid'];
$records[$key] = $row;
}
unset($result);
$search[$s] = $source->get_search_set();
}
$_SESSION['search'][$search_request] = $search;
// create resultset object
$count = count($records);
- $first = ($page-1) * $CONFIG['pagesize'];
+ $first = ($page-1) * $PAGE_SIZE;
$result = new rcube_result_set($count, $first);
// get records from the next page to add to the list
- $pages = ceil((count($records) + $delcnt) / $CONFIG['pagesize']);
+ $pages = ceil((count($records) + $delcnt) / $PAGE_SIZE);
if ($_GET['_from'] != 'show' && $pages > 1 && $page < $pages) {
// sort the records
ksort($records, SORT_LOCALE_STRING);
- $first += $CONFIG['pagesize'];
+ $first += $PAGE_SIZE;
// create resultset object
$res = new rcube_result_set($count, $first - $delcnt);
- if ($CONFIG['pagesize'] < $count) {
+ if ($PAGE_SIZE < $count) {
$records = array_slice($records, $first - $delcnt, $delcnt);
}
$res->records = array_values($records);
$records = $res;
}
else {
unset($records);
}
}
else {
// count contacts for this user
$result = $CONTACTS->count();
// get records from the next page to add to the list
- $pages = ceil(($result->count + $delcnt) / $CONFIG['pagesize']);
+ $pages = ceil(($result->count + $delcnt) / $PAGE_SIZE);
if ($_GET['_from'] != 'show' && $pages > 1 && $page < $pages) {
$CONTACTS->set_page($page);
$records = $CONTACTS->list_records(null, -$delcnt);
}
}
// update message count display
-$OUTPUT->set_env('pagecount', ceil($result->count / $CONFIG['pagesize']));
+$OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
if (!empty($_SESSION['contact_undo'])) {
$_SESSION['contact_undo']['ts'] = time();
$msg = html::span(null, rcube_label('contactdeleted'))
. ' ' . html::a(array('onclick' => JS_OBJECT_NAME.".command('undo', '', this)"), rcube_label('undo'));
$OUTPUT->show_message($msg, 'confirmation', null, true, $undo_time);
}
else {
$OUTPUT->show_message('contactdeleted', 'confirmation');
}
// add new rows from next page (if any)
if (!empty($records)) {
rcmail_js_contacts_list($records);
}
// send response
$OUTPUT->send();
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index fac8987f8..e7d3eaebe 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -1,803 +1,807 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/addressbook/func.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2007, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Provide addressbook functionality and GUI objects |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
$SEARCH_MODS_DEFAULT = array('name'=>1, 'firstname'=>1, 'surname'=>1, 'email'=>1, '*'=>1);
// general definition of contact coltypes
$CONTACT_COLTYPES = array(
'name' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('name'), 'category' => 'main'),
'firstname' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('firstname'), 'category' => 'main'),
'surname' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('surname'), 'category' => 'main'),
'email' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('email'), 'subtypes' => array('home','work','other'), 'category' => 'main'),
'middlename' => array('type' => 'text', 'size' => 19, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('middlename'), 'category' => 'main'),
'prefix' => array('type' => 'text', 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => rcube_label('nameprefix'), 'category' => 'main'),
'suffix' => array('type' => 'text', 'size' => 8, 'maxlength' => 20, 'limit' => 1, 'label' => rcube_label('namesuffix'), 'category' => 'main'),
'nickname' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('nickname'), 'category' => 'main'),
'jobtitle' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('jobtitle'), 'category' => 'main'),
'organization' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('organization'), 'category' => 'main'),
'department' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('department'), 'category' => 'main'),
'gender' => array('type' => 'select', 'limit' => 1, 'label' => rcube_label('gender'), 'options' => array('male' => rcube_label('male'), 'female' => rcube_label('female')), 'category' => 'personal'),
'maidenname' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('maidenname'), 'category' => 'personal'),
'phone' => array('type' => 'text', 'size' => 40, 'maxlength' => 20, 'label' => rcube_label('phone'), 'subtypes' => array('home','home2','work','work2','mobile','main','homefax','workfax','car','pager','video','assistant','other'), 'category' => 'main'),
'address' => array('type' => 'composite', 'label' => rcube_label('address'), 'subtypes' => array('home','work','other'), 'childs' => array(
'street' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('street'), 'category' => 'main'),
'locality' => array('type' => 'text', 'size' => 28, 'maxlength' => 50, 'label' => rcube_label('locality'), 'category' => 'main'),
'zipcode' => array('type' => 'text', 'size' => 8, 'maxlength' => 15, 'label' => rcube_label('zipcode'), 'category' => 'main'),
'region' => array('type' => 'text', 'size' => 12, 'maxlength' => 50, 'label' => rcube_label('region'), 'category' => 'main'),
'country' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('country'), 'category' => 'main'),
), 'category' => 'main'),
'birthday' => array('type' => 'date', 'size' => 12, 'maxlength' => 16, 'label' => rcube_label('birthday'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col', 'category' => 'personal'),
'anniversary' => array('type' => 'date', 'size' => 12, 'maxlength' => 16, 'label' => rcube_label('anniversary'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col', 'category' => 'personal'),
'website' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('website'), 'subtypes' => array('homepage','work','blog','profile','other'), 'category' => 'main'),
'im' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'label' => rcube_label('instantmessenger'), 'subtypes' => array('aim','icq','msn','yahoo','jabber','skype','other'), 'category' => 'main'),
'notes' => array('type' => 'textarea', 'size' => 40, 'rows' => 15, 'maxlength' => 500, 'label' => rcube_label('notes'), 'limit' => 1),
'photo' => array('type' => 'image', 'limit' => 1, 'category' => 'main'),
'assistant' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('assistant'), 'category' => 'personal'),
'manager' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('manager'), 'category' => 'personal'),
'spouse' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1, 'label' => rcube_label('spouse'), 'category' => 'personal'),
// TODO: define fields for vcards like GEO, KEY
);
+$PAGE_SIZE = $RCMAIL->config->get('addressbook_pagesize');
+if (!$PAGE_SIZE) {
+ $PAGE_SIZE = $RCMAIL->config->get('pagesize', 50);
+}
// Addressbook UI
if (!$RCMAIL->action && !$OUTPUT->ajax_call) {
// add list of address sources to client env
$js_list = $RCMAIL->get_address_sources();
$source = get_input_value('_source', RCUBE_INPUT_GPC);
// use first directory by default
if (!strlen($source) || !isset($js_list[$source]))
$source = $js_list[key($js_list)]['id'];
// count all/writeable sources
$writeable = 0;
$count = 0;
foreach ($js_list as $sid => $s) {
$count++;
if (!$s['readonly']) {
$writeable++;
}
// unset hidden sources
if ($s['hidden']) {
unset($js_list[$sid]);
}
}
$search_mods = $RCMAIL->config->get('addressbook_search_mods', $SEARCH_MODS_DEFAULT);
$OUTPUT->set_env('search_mods', $search_mods);
$OUTPUT->set_env('address_sources', $js_list);
$OUTPUT->set_env('writable_source', $writeable);
$OUTPUT->set_pagetitle(rcube_label('addressbook'));
$_SESSION['addressbooks_count'] = $count;
$_SESSION['addressbooks_count_writeable'] = $writeable;
if (!strlen($source))
$source = strval(key($js_list));
$CONTACTS = rcmail_contact_source($source, true);
}
// remove undo information...
if ($undo = $_SESSION['contact_undo']) {
// ...after timeout
$undo_time = $RCMAIL->config->get('undo_timeout', 0);
if ($undo['ts'] < time() - $undo_time)
$RCMAIL->session->remove('contact_undo');
}
// instantiate a contacts object according to the given source
function rcmail_contact_source($source=null, $init_env=false, $writable=false)
{
- global $RCMAIL, $OUTPUT, $CONFIG, $CONTACT_COLTYPES;
+ global $RCMAIL, $OUTPUT, $CONTACT_COLTYPES, $PAGE_SIZE;
if (!strlen($source)) {
$source = get_input_value('_source', RCUBE_INPUT_GPC);
}
// Get object
$CONTACTS = $RCMAIL->get_address_book($source, $writable);
- $CONTACTS->set_pagesize($CONFIG['pagesize']);
+ $CONTACTS->set_pagesize($PAGE_SIZE);
// set list properties and session vars
if (!empty($_GET['_page']))
$CONTACTS->set_page(($_SESSION['page'] = intval($_GET['_page'])));
else
$CONTACTS->set_page(isset($_SESSION['page']) ? $_SESSION['page'] : 1);
if (!empty($_REQUEST['_gid']))
$CONTACTS->set_group(get_input_value('_gid', RCUBE_INPUT_GPC));
if (!$init_env)
return $CONTACTS;
$OUTPUT->set_env('readonly', $CONTACTS->readonly);
$OUTPUT->set_env('source', $source);
// reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object
if (is_array($CONTACTS->coltypes)) {
// remove cols not listed by the backend class
$contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
$CONTACT_COLTYPES = array_intersect_key($CONTACT_COLTYPES, $contact_cols);
// add associative coltypes definition
if (!$CONTACTS->coltypes[0]) {
foreach ($CONTACTS->coltypes as $col => $colprop) {
if (is_array($colprop['childs'])) {
foreach ($colprop['childs'] as $childcol => $childprop)
$colprop['childs'][$childcol] = array_merge((array)$CONTACT_COLTYPES[$col]['childs'][$childcol], $childprop);
}
$CONTACT_COLTYPES[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], $colprop) : $colprop;
}
}
}
$OUTPUT->set_env('photocol', is_array($CONTACT_COLTYPES['photo']));
return $CONTACTS;
}
function rcmail_default_source($writable=false)
{
global $RCMAIL;
// get list of address sources
$first = reset($RCMAIL->get_address_sources($writable));
// use first directory by default
return $first['id'];
}
function rcmail_set_sourcename($abook)
{
global $OUTPUT;
// get address book name (for display)
if ($abook && $_SESSION['addressbooks_count'] > 1) {
$name = $abook->get_name();
if (!$name && $source == 0) {
$name = rcube_label('personaladrbook');
}
$OUTPUT->set_env('sourcename', $name);
}
}
function rcmail_directory_list($attrib)
{
global $RCMAIL, $OUTPUT;
if (!$attrib['id'])
$attrib['id'] = 'rcmdirectorylist';
$out = '';
$local_id = '0';
$jsdata = array();
$line_templ = html::tag('li', array(
'id' => 'rcmli%s', 'class' => '%s'),
html::a(array('href' => '%s',
'rel' => '%s',
'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
$sources = (array) $OUTPUT->env['address_sources'];
reset($sources);
// currently selected source
$current = get_input_value('_source', RCUBE_INPUT_GPC);
foreach ($sources as $j => $source) {
$id = strval(strlen($source['id']) ? $source['id'] : $j);
$js_id = JQ($id);
// set class name(s)
$class_name = 'addressbook';
if ($current === $id)
$class_name .= ' selected';
if ($source['readonly'])
$class_name .= ' readonly';
if ($source['class_name'])
$class_name .= ' ' . $source['class_name'];
$out .= sprintf($line_templ,
html_identifier($id),
$class_name,
Q(rcmail_url(null, array('_source' => $id))),
$source['id'],
$js_id, (!empty($source['name']) ? Q($source['name']) : Q($id)));
$groupdata = array('out' => $out, 'jsdata' => $jsdata, 'source' => $id);
if ($source['groups'])
$groupdata = rcmail_contact_groups($groupdata);
$jsdata = $groupdata['jsdata'];
$out = $groupdata['out'];
}
$line_templ = html::tag('li', array(
'id' => 'rcmliS%s', 'class' => '%s'),
html::a(array('href' => '#', 'rel' => 'S%s',
'onclick' => "return ".JS_OBJECT_NAME.".command('listsearch', '%s', this)"), '%s'));
// Saved searches
$sources = $RCMAIL->user->list_searches(rcube_user::SEARCH_ADDRESSBOOK);
foreach ($sources as $j => $source) {
$id = $source['id'];
$js_id = JQ($id);
// set class name(s)
$class_name = 'contactsearch';
if ($current === $id)
$class_name .= ' selected';
if ($source['class_name'])
$class_name .= ' ' . $source['class_name'];
$out .= sprintf($line_templ,
html_identifier($id),
$class_name,
$id,
$js_id, (!empty($source['name']) ? Q($source['name']) : Q($id)));
}
$OUTPUT->set_env('contactgroups', $jsdata);
$OUTPUT->add_gui_object('folderlist', $attrib['id']);
// add some labels to client
$OUTPUT->add_label('deletegroupconfirm', 'groupdeleting', 'addingmember', 'removingmember');
return html::tag('ul', $attrib, $out, html::$common_attrib);
}
function rcmail_contact_groups($args)
{
global $RCMAIL;
$groups = $RCMAIL->get_address_book($args['source'])->list_groups();
if (!empty($groups)) {
$line_templ = html::tag('li', array(
'id' => 'rcmliG%s', 'class' => 'contactgroup'),
html::a(array('href' => '#',
'rel' => '%s:%s',
'onclick' => "return ".JS_OBJECT_NAME.".command('listgroup',{'source':'%s','id':'%s'},this)"), '%s'));
$jsdata = array();
foreach ($groups as $group) {
$args['out'] .= sprintf($line_templ,
html_identifier($args['source'] . $group['ID']),
$args['source'], $group['ID'],
$args['source'], $group['ID'], Q($group['name'])
);
$args['jsdata']['G'.$args['source'].$group['ID']] = array(
'source' => $args['source'], 'id' => $group['ID'],
'name' => $group['name'], 'type' => 'group');
}
}
return $args;
}
// return the contacts list as HTML table
function rcmail_contacts_list($attrib)
{
global $CONTACTS, $OUTPUT;
// define list of cols to be displayed
$a_show_cols = array('name');
// add id to message list table if not specified
if (!strlen($attrib['id']))
$attrib['id'] = 'rcmAddressList';
// create XHTML table
$out = rcube_table_output($attrib, array(), $a_show_cols, $CONTACTS->primary_key);
// set client env
$OUTPUT->add_gui_object('contactslist', $attrib['id']);
$OUTPUT->set_env('current_page', (int)$CONTACTS->list_page);
$OUTPUT->include_script('list.js');
// add some labels to client
$OUTPUT->add_label('deletecontactconfirm', 'copyingcontact', 'contactdeleting');
return $out;
}
function rcmail_js_contacts_list($result, $prefix='')
{
global $OUTPUT;
if (empty($result) || $result->count == 0)
return;
// define list of cols to be displayed
$a_show_cols = array('name');
while ($row = $result->next()) {
$a_row_cols = array();
// build contact ID with source ID
if (isset($row['sourceid'])) {
$row['ID'] = $row['ID'].'-'.$row['sourceid'];
}
// format each col
foreach ($a_show_cols as $col) {
$val = $row[$col];
if ($val == '' && $col == 'name') {
$val = rcube_addressbook::compose_display_name($row, true);
}
$a_row_cols[$col] = Q($val);
}
$OUTPUT->command($prefix.'add_contact_row', $row['ID'], $a_row_cols);
}
}
// similar function as /steps/settings/identities.inc::rcmail_identity_frame()
function rcmail_contact_frame($attrib)
{
global $OUTPUT;
if (!$attrib['id'])
$attrib['id'] = 'rcmcontactframe';
$attrib['name'] = $attrib['id'];
$OUTPUT->set_env('contentframe', $attrib['name']);
$OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/blank.gif');
return html::iframe($attrib);
}
function rcmail_rowcount_display($attrib)
{
global $OUTPUT;
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$OUTPUT->add_gui_object('countdisplay', $attrib['id']);
if ($attrib['label'])
$_SESSION['contactcountdisplay'] = $attrib['label'];
return html::span($attrib, rcube_label('loading'));
}
function rcmail_get_rowcount_text($result=null)
{
- global $CONTACTS, $CONFIG;
+ global $CONTACTS, $PAGE_SIZE;
// read nr of contacts
if (!$result) {
$result = $CONTACTS->get_result();
}
if ($result->count == 0)
$out = rcube_label('nocontactsfound');
else
$out = rcube_label(array(
'name' => $_SESSION['contactcountdisplay'] ? $_SESSION['contactcountdisplay'] : 'contactsfromto',
'vars' => array(
'from' => $result->first + 1,
- 'to' => min($result->count, $result->first + $CONFIG['pagesize']),
+ 'to' => min($result->count, $result->first + $PAGE_SIZE),
'count' => $result->count)
));
return $out;
}
function rcmail_get_type_label($type)
{
$label = 'type'.$type;
if (rcube_label_exists($label, '*', $domain))
return rcube_label($label, $domain);
else if (preg_match('/\w+(\d+)$/', $label, $m)
&& ($label = preg_replace('/(\d+)$/', '', $label))
&& rcube_label_exists($label, '*', $domain))
return rcube_label($label, $domain) . ' ' . $m[1];
return ucfirst($type);
}
function rcmail_contact_form($form, $record, $attrib = null)
{
global $RCMAIL, $CONFIG;
static $jqueryui_loaded = 0;
// Allow plugins to modify contact form content
$plugin = $RCMAIL->plugins->exec_hook('contact_form', array(
'form' => $form, 'record' => $record));
$form = $plugin['form'];
$record = $plugin['record'];
$edit_mode = $RCMAIL->action != 'show';
$del_button = $attrib['deleteicon'] ? html::img(array('src' => $CONFIG['skin_path'] . $attrib['deleteicon'], 'alt' => rcube_label('delete'))) : rcube_label('delete');
unset($attrib['deleteicon']);
$out = '';
// get default coltypes
$coltypes = $GLOBALS['CONTACT_COLTYPES'];
$coltype_labels = array();
foreach ($coltypes as $col => $prop) {
if ($prop['subtypes']) {
$subtype_names = array_map('rcmail_get_type_label', $prop['subtypes']);
$select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype'));
$select_subtype->add($subtype_names, $prop['subtypes']);
$coltypes[$col]['subtypes_select'] = $select_subtype->show();
}
if ($prop['childs']) {
foreach ($prop['childs'] as $childcol => $cp)
$coltype_labels[$childcol] = array('label' => $cp['label']);
}
}
foreach ($form as $section => $fieldset) {
// skip empty sections
if (empty($fieldset['content']))
continue;
$select_add = new html_select(array('class' => 'addfieldmenu', 'rel' => $section));
$select_add->add(rcube_label('addfield'), '');
// render head section with name fields (not a regular list of rows)
if ($section == 'head') {
$content = '';
// unset display name if it is composed from name parts
if ($record['name'] == rcube_addressbook::compose_display_name(array('name' => '') + (array)$record))
unset($record['name']);
// group fields
$field_blocks = array(
'names' => array('prefix','firstname','middlename','surname','suffix'),
'displayname' => array('name'),
'nickname' => array('nickname'),
'organization' => array('organization'),
'department' => array('department'),
'jobtitle' => array('jobtitle'),
);
foreach ($field_blocks as $blockname => $colnames) {
$fields = '';
foreach ($colnames as $col) {
// skip cols unknown to the backend
if (!$coltypes[$col])
continue;
// only string values are expected here
if (is_array($record[$col]))
$record[$col] = join(' ', $record[$col]);
if ($RCMAIL->action == 'show') {
if (!empty($record[$col]))
$fields .= html::span('namefield ' . $col, Q($record[$col])) . " ";
}
else {
$colprop = (array)$fieldset['content'][$col] + (array)$coltypes[$col];
$colprop['id'] = 'ff_'.$col;
if (empty($record[$col]) && !$colprop['visible']) {
$colprop['style'] = 'display:none';
$select_add->add($colprop['label'], $col);
}
$fields .= rcmail_get_edit_field($col, $record[$col], $colprop, $colprop['type']);
}
}
$content .= html::div($blockname, $fields);
}
if ($edit_mode)
$content .= html::p('addfield', $select_add->show(null));
$out .= html::tag('fieldset', $attrib, (!empty($fieldset['name']) ? html::tag('legend', null, Q($fieldset['name'])) : '') . $content) ."\n";
continue;
}
$content = '';
if (is_array($fieldset['content'])) {
foreach ($fieldset['content'] as $col => $colprop) {
// remove subtype part of col name
list($field, $subtype) = explode(':', $col);
if (!$subtype) $subtype = 'home';
$fullkey = $col.':'.$subtype;
// skip cols unknown to the backend
if (!$coltypes[$field])
continue;
// merge colprop with global coltype configuration
$colprop += $coltypes[$field];
$label = isset($colprop['label']) ? $colprop['label'] : rcube_label($col);
// prepare subtype selector in edit mode
if ($edit_mode && is_array($colprop['subtypes'])) {
$subtype_names = array_map('rcmail_get_type_label', $colprop['subtypes']);
$select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype'));
$select_subtype->add($subtype_names, $colprop['subtypes']);
}
else
$select_subtype = null;
if (!empty($colprop['value'])) {
$values = (array)$colprop['value'];
}
else {
// iterate over possible subtypes and collect values with their subtype
if (is_array($colprop['subtypes'])) {
$values = $subtypes = array();
foreach ($colprop['subtypes'] as $i => $st) {
$newval = false;
if ($record[$field.':'.$st]) {
$subtypes[count($values)] = $st;
$newval = $record[$field.':'.$st];
}
else if ($i == 0 && $record[$field]) {
$subtypes[count($values)] = $st;
$newval = $record[$field];
}
if ($newval !== false) {
if (is_array($newval) && isset($newval[0]))
$values = array_merge($values, $newval);
else
$values[] = $newval;
}
}
}
else {
$values = $record[$fullkey] ? $record[$fullkey] : $record[$field];
$subtypes = null;
}
}
// hack: create empty values array to force this field to be displayed
if (empty($values) && $colprop['visible'])
$values[] = '';
$rows = '';
foreach ((array)$values as $i => $val) {
if ($subtypes[$i])
$subtype = $subtypes[$i];
// render composite field
if ($colprop['type'] == 'composite') {
$composite = array(); $j = 0;
$template = $RCMAIL->config->get($col . '_template', '{'.join('} {', array_keys($colprop['childs'])).'}');
foreach ($colprop['childs'] as $childcol => $cp) {
$childvalue = $val[$childcol] ? $val[$childcol] : $val[$j];
if ($edit_mode) {
if ($colprop['subtypes'] || $colprop['limit'] != 1) $cp['array'] = true;
$composite['{'.$childcol.'}'] = rcmail_get_edit_field($childcol, $childvalue, $cp, $cp['type']) . " ";
}
else {
$childval = $cp['render_func'] ? call_user_func($cp['render_func'], $childvalue, $childcol) : Q($childvalue);
$composite['{'.$childcol.'}'] = html::span('data ' . $childcol, $childval) . " ";
}
$j++;
}
$coltypes[$field] += (array)$colprop;
$coltypes[$field]['count']++;
$val = preg_replace('/\{\w+\}/', '', strtr($template, $composite));
}
else if ($edit_mode) {
// call callback to render/format value
if ($colprop['render_func'])
$val = call_user_func($colprop['render_func'], $val, $col);
$coltypes[$field] = (array)$colprop + $coltypes[$field];
if ($colprop['subtypes'] || $colprop['limit'] != 1)
$colprop['array'] = true;
// load jquery UI datepickert for date fields
if ($colprop['type'] == 'date') {
if (!$jqueryui_loaded++) {
$RCMAIL->plugins->load_plugin('jqueryui');
$RCMAIL->output->set_env('date_format', strtr($RCMAIL->config->get('date_format', 'Y-m-d'), array('y'=>'y', 'Y'=>'yy', 'm'=>'mm', 'n'=>'m', 'd'=>'dd', 'j'=>'d')));
foreach (array('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec') as $month)
$month_names[] = rcube_label($month);
$RCMAIL->output->set_env('month_names', $month_names);
}
$colprop['class'] .= ($colprop['class'] ? ' ' : '') . 'datepicker';
$val = format_date($val, $RCMAIL->config->get('date_format', 'Y-m-d'), false);
}
$val = rcmail_get_edit_field($col, $val, $colprop, $colprop['type']);
$coltypes[$field]['count']++;
}
else if ($colprop['render_func'])
$val = call_user_func($colprop['render_func'], $val, $col);
else if (is_array($colprop['options']) && isset($colprop['options'][$val]))
$val = $colprop['options'][$val];
else
$val = Q($val);
// use subtype as label
if ($colprop['subtypes'])
$label = rcmail_get_type_label($subtype);
// add delete button/link
if ($edit_mode && !($colprop['visible'] && $colprop['limit'] == 1))
$val .= html::a(array('href' => '#del', 'class' => 'contactfieldbutton deletebutton', 'title' => rcube_label('delete'), 'rel' => $col), $del_button);
// display row with label
if ($label) {
$rows .= html::div('row',
html::div('contactfieldlabel label', $select_subtype ? $select_subtype->show($subtype) : Q($label)) .
html::div('contactfieldcontent '.$colprop['type'], $val));
}
else // row without label
$rows .= html::div('row', html::div('contactfield', $val));
}
// add option to the add-field menu
if (!$colprop['limit'] || $coltypes[$field]['count'] < $colprop['limit']) {
$select_add->add($colprop['label'], $col);
$select_add->_count++;
}
// wrap rows in fieldgroup container
if ($rows) {
$content .= html::tag('fieldset', array('class' => 'contactfieldgroup ' . ($colprop['subtypes'] ? 'contactfieldgroupmulti ' : '') . 'contactcontroller' . $col, 'style' => ($rows ? null : 'display:none')),
($colprop['subtypes'] ? html::tag('legend', null, Q($colprop['label'])) : ' ') .
$rows);
}
}
if (!$content)
continue;
// also render add-field selector
if ($edit_mode)
$content .= html::p('addfield', $select_add->show(null, array('style' => $select_add->_count ? null : 'display:none')));
$content = html::div(array('id' => 'contactsection' . $section), $content);
}
else {
$content = $fieldset['content'];
}
if ($content)
$out .= html::tag('fieldset', null, html::tag('legend', null, Q($fieldset['name'])) . $content) ."\n";
}
if ($edit_mode) {
$RCMAIL->output->set_env('coltypes', $coltypes + $coltype_labels);
$RCMAIL->output->set_env('delbutton', $del_button);
$RCMAIL->output->add_label('delete');
}
return $out;
}
function rcmail_contact_photo($attrib)
{
global $SOURCE_ID, $CONTACTS, $CONTACT_COLTYPES, $RCMAIL, $CONFIG;
if ($result = $CONTACTS->get_result())
$record = $result->first();
$photo_img = $attrib['placeholder'] ? $CONFIG['skin_path'] . $attrib['placeholder'] : 'program/blank.gif';
$RCMAIL->output->set_env('photo_placeholder', $photo_img);
unset($attrib['placeholder']);
if (preg_match('!^https?://!i', $record['photo']))
$photo_img = $record['photo'];
else if ($record['photo'])
$photo_img = $RCMAIL->url(array('_action' => 'photo', '_cid' => $record['ID'], '_source' => $SOURCE_ID));
else
$ff_value = '-del-'; // will disable delete-photo action
$img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => ''));
$content = html::div($attrib, $img);
if ($CONTACT_COLTYPES['photo'] && ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add')) {
$RCMAIL->output->add_gui_object('contactphoto', $attrib['id']);
$hidden = new html_hiddenfield(array('name' => '_photo', 'id' => 'ff_photo', 'value' => $ff_value));
$content .= $hidden->show();
}
return $content;
}
function rcmail_format_date_col($val)
{
global $RCMAIL;
return format_date($val, $RCMAIL->config->get('date_format', 'Y-m-d'), false);
}
/**
* Returns contact ID(s) and source(s) from GET/POST data
*
* @return array List of contact IDs per-source
*/
function rcmail_get_cids()
{
// contact ID (or comma-separated list of IDs) is provided in two
// forms. If _source is an empty string then the ID is a string
// containing contact ID and source name in form: <ID>-<SOURCE>
$cid = get_input_value('_cid', RCUBE_INPUT_GPC);
$source = (string) get_input_value('_source', RCUBE_INPUT_GPC);
if (!preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid)) {
return array();
}
$cid = explode(',', $cid);
$got_source = strlen($source);
$result = array();
// create per-source contact IDs array
foreach ($cid as $id) {
// if _source is not specified we'll find it from decoded ID
if (!$got_source) {
if ($sep = strrpos($id, '-')) {
$contact_id = substr($id, 0, $sep);
$source_id = substr($id, $sep+1);
if (strlen($source_id)) {
$result[(string)$source_id][] = $contact_id;
}
}
}
else {
$result[$source][] = $id;
}
}
return $result;
}
// register UI objects
$OUTPUT->add_handlers(array(
'directorylist' => 'rcmail_directory_list',
// 'groupslist' => 'rcmail_contact_groups',
'addresslist' => 'rcmail_contacts_list',
'addressframe' => 'rcmail_contact_frame',
'recordscountdisplay' => 'rcmail_rowcount_display',
'searchform' => array($OUTPUT, 'search_form')
));
// register action aliases
$RCMAIL->register_action_map(array(
'add' => 'edit.inc',
'photo' => 'show.inc',
'group-create' => 'groups.inc',
'group-rename' => 'groups.inc',
'group-delete' => 'groups.inc',
'group-addmembers' => 'groups.inc',
'group-delmembers' => 'groups.inc',
'search-create' => 'search.inc',
'search-delete' => 'search.inc',
));
diff --git a/program/steps/addressbook/list.inc b/program/steps/addressbook/list.inc
index b4b7306a6..0e3afa2a9 100644
--- a/program/steps/addressbook/list.inc
+++ b/program/steps/addressbook/list.inc
@@ -1,91 +1,91 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/addressbook/list.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2007, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Send contacts list to client (as remote response) |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
// Use search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
{
$search = (array)$_SESSION['search'][$_REQUEST['_search']];
$records = array();
if (!empty($_GET['_page']))
$page = intval($_GET['_page']);
else
$page = isset($_SESSION['page']) ? $_SESSION['page'] : 1;
$_SESSION['page'] = $page;
// Get records from all sources
foreach ($search as $s => $set) {
$source = $RCMAIL->get_address_book($s);
// reset page
$source->set_page(1);
$source->set_pagesize(9999);
$source->set_search_set($set);
// get records
$result = $source->list_records(array('name', 'email'));
while ($row = $result->next()) {
$row['sourceid'] = $s;
$key = $row['name'] . ':' . $row['sourceid'];
$records[$key] = $row;
}
unset($result);
}
// sort the records
ksort($records, SORT_LOCALE_STRING);
// create resultset object
- $count = count($records);
- $first = ($page-1) * $CONFIG['pagesize'];
+ $count = count($records);
+ $first = ($page-1) * $PAGE_SIZE;
$result = new rcube_result_set($count, $first);
// we need only records for current page
- if ($CONFIG['pagesize'] < $count) {
- $records = array_slice($records, $first, $CONFIG['pagesize']);
+ if ($PAGE_SIZE < $count) {
+ $records = array_slice($records, $first, $PAGE_SIZE);
}
$result->records = array_values($records);
}
// List selected directory
else {
$CONTACTS = rcmail_contact_source(null, true);
// get contacts for this user
$result = $CONTACTS->list_records(array('name'));
-
+
if (!$result->count && $result->searchonly) {
$OUTPUT->show_message('contactsearchonly', 'notice');
$OUTPUT->command('command', 'advanced-search');
}
}
// update message count display
-$OUTPUT->set_env('pagecount', ceil($result->count / $CONFIG['pagesize']));
+$OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
// create javascript list
rcmail_js_contacts_list($result);
// send response
$OUTPUT->send();
diff --git a/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc
index 643cc60a8..e4ce2d655 100644
--- a/program/steps/addressbook/search.inc
+++ b/program/steps/addressbook/search.inc
@@ -1,332 +1,332 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/addressbook/search.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Search action (and form) for address book contacts |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
$Id: search.inc 456 2007-01-10 12:34:33Z thomasb $
*/
if ($RCMAIL->action == 'search-create') {
$id = get_input_value('_search', RCUBE_INPUT_POST);
$name = get_input_value('_name', RCUBE_INPUT_POST, true);
if (($params = $_SESSION['search_params']) && $params['id'] == $id) {
$data = array(
'type' => rcube_user::SEARCH_ADDRESSBOOK,
'name' => $name,
'data' => array(
'fields' => $params['data'][0],
'search' => $params['data'][1],
),
);
$plugin = $RCMAIL->plugins->exec_hook('saved_search_create', array('data' => $data));
if (!$plugin['abort'])
$result = $RCMAIL->user->insert_search($plugin['data']);
else
$result = $plugin['result'];
}
if ($result) {
$OUTPUT->show_message('savedsearchcreated', 'confirmation');
$OUTPUT->command('insert_saved_search', Q($name), Q($result));
}
else
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'savedsearchcreateerror', 'error');
$OUTPUT->send();
}
if ($RCMAIL->action == 'search-delete') {
$id = get_input_value('_sid', RCUBE_INPUT_POST);
$plugin = $RCMAIL->plugins->exec_hook('saved_search_delete', array('id' => $id));
if (!$plugin['abort'])
$result = $RCMAIL->user->delete_search($id);
else
$result = $plugin['result'];
if ($result) {
$OUTPUT->show_message('savedsearchdeleted', 'confirmation');
$OUTPUT->command('remove_search_item', Q($id));
// contact list will be cleared, clear also page counter
$OUTPUT->command('set_rowcount', rcube_label('nocontactsfound'));
$OUTPUT->set_env('pagecount', 0);
}
else
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'savedsearchdeleteerror', 'error');
$OUTPUT->send();
}
if (!isset($_GET['_form'])) {
rcmail_contact_search();
}
$OUTPUT->add_handler('searchform', 'rcmail_contact_search_form');
$OUTPUT->send('contactsearch');
function rcmail_contact_search()
{
- global $RCMAIL, $OUTPUT, $CONFIG, $SEARCH_MODS_DEFAULT;
+ global $RCMAIL, $OUTPUT, $SEARCH_MODS_DEFAULT, $PAGE_SIZE;
$adv = isset($_POST['_adv']);
$sid = get_input_value('_sid', RCUBE_INPUT_GET);
// get search criteria from saved search
if ($sid && ($search = $RCMAIL->user->get_search($sid))) {
$fields = $search['data']['fields'];
$search = $search['data']['search'];
}
// get fields/values from advanced search form
else if ($adv) {
foreach (array_keys($_POST) as $key) {
$s = trim(get_input_value($key, RCUBE_INPUT_POST, true));
if (strlen($s) && preg_match('/^_search_([a-zA-Z0-9_-]+)$/', $key, $m)) {
$search[] = $s;
$fields[] = $m[1];
}
}
if (empty($fields)) {
// do nothing, show the form again
return;
}
}
// quick-search
else {
$search = trim(get_input_value('_q', RCUBE_INPUT_GET, true));
$fields = explode(',', get_input_value('_headers', RCUBE_INPUT_GET));
if (empty($fields)) {
$fields = array_keys($SEARCH_MODS_DEFAULT);
}
else {
$fields = array_filter($fields);
}
// update search_mods setting
$old_mods = $RCMAIL->config->get('addressbook_search_mods');
$search_mods = array_fill_keys($fields, 1);
if ($old_mods != $search_mods) {
$RCMAIL->user->save_prefs(array('addressbook_search_mods' => $search_mods));
}
if (in_array('*', $fields)) {
$fields = '*';
}
}
// Values matching mode
$mode = (int) $RCMAIL->config->get('addressbook_search_mode');
// get sources list
$sources = $RCMAIL->get_address_sources();
$search_set = array();
$records = array();
foreach ($sources as $s) {
$source = $RCMAIL->get_address_book($s['id']);
// check if search fields are supported....
if (is_array($fields)) {
$cols = $source->coltypes[0] ? array_flip($source->coltypes) : $source->coltypes;
$supported = 0;
foreach ($fields as $f) {
if (array_key_exists($f, $cols)) {
$supported ++;
}
}
// in advanced search we require all fields (AND operator)
// in quick search we require at least one field (OR operator)
if (($adv && $supported < count($fields)) || (!$adv && !$supported)) {
continue;
}
}
// reset page
$source->set_page(1);
$source->set_pagesize(9999);
// get contacts count
$result = $source->search($fields, $search, $mode, false);
if (!$result->count) {
continue;
}
// get records
$result = $source->list_records(array('name', 'email'));
while ($row = $result->next()) {
$row['sourceid'] = $s['id'];
$key = $row['name'] . ':' . $row['sourceid'];
$records[$key] = $row;
}
unset($result);
$search_set[$s['id']] = $source->get_search_set();
}
// sort the records
ksort($records, SORT_LOCALE_STRING);
// create resultset object
$count = count($records);
$result = new rcube_result_set($count);
// cut first-page records
- if ($CONFIG['pagesize'] < $count) {
- $records = array_slice($records, 0, $CONFIG['pagesize']);
+ if ($PAGE_SIZE < $count) {
+ $records = array_slice($records, 0, $PAGE_SIZE);
}
$result->records = array_values($records);
// search request ID
$search_request = md5('addr'
.(is_array($fields) ? implode($fields, ',') : $fields)
.(is_array($search) ? implode($search, ',') : $search));
// save search settings in session
$_SESSION['search'][$search_request] = $search_set;
$_SESSION['search_params'] = array('id' => $search_request, 'data' => array($fields, $search));
$_SESSION['page'] = 1;
if ($adv)
$OUTPUT->command('list_contacts_clear');
if ($result->count > 0) {
// create javascript list
rcmail_js_contacts_list($result);
$OUTPUT->show_message('contactsearchsuccessful', 'confirmation', array('nr' => $result->count));
}
else {
$OUTPUT->show_message('nocontactsfound', 'notice');
}
// update message count display
$OUTPUT->command('set_env', 'search_request', $search_request);
- $OUTPUT->command('set_env', 'pagecount', ceil($result->count / $CONFIG['pagesize']));
+ $OUTPUT->command('set_env', 'pagecount', ceil($result->count / $PAGE_SIZE));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
// Re-set current source
$OUTPUT->command('set_env', 'search_id', $sid);
$OUTPUT->command('set_env', 'source', '');
$OUTPUT->command('set_env', 'group', '');
// unselect currently selected directory/group
if (!$sid)
$OUTPUT->command('unselect_directory');
$OUTPUT->command('update_group_commands');
// send response
$OUTPUT->send($adv ? 'iframe' : null);
}
function rcmail_contact_search_form($attrib)
{
global $RCMAIL, $CONTACT_COLTYPES;
$i_size = !empty($attrib['size']) ? $attrib['size'] : 30;
$form = array(
'main' => array(
'name' => rcube_label('contactproperties'),
'content' => array(
),
),
'personal' => array(
'name' => rcube_label('personalinfo'),
'content' => array(
),
),
'other' => array(
'name' => rcube_label('other'),
'content' => array(
),
),
);
// get supported coltypes from all address sources
$sources = $RCMAIL->get_address_sources();
$coltypes = array();
foreach ($sources as $s) {
$CONTACTS = $RCMAIL->get_address_book($s['id']);
if (is_array($CONTACTS->coltypes)) {
$contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
$coltypes = array_merge($coltypes, $contact_cols);
}
}
// merge supported coltypes with $CONTACT_COLTYPES
foreach ($coltypes as $col => $colprop) {
$coltypes[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], (array)$colprop) : (array)$colprop;
}
// build form fields list
foreach ($coltypes as $col => $colprop)
{
if ($colprop['type'] != 'image' && !$colprop['nosearch'])
{
$ftype = $colprop['type'] == 'select' ? 'select' : 'text';
$label = isset($colprop['label']) ? $colprop['label'] : rcube_label($col);
$category = $colprop['category'] ? $colprop['category'] : 'other';
if ($ftype == 'text')
$colprop['size'] = $i_size;
$content = html::div('row', html::div('contactfieldlabel label', Q($label))
. html::div('contactfieldcontent', rcmail_get_edit_field('search_'.$col, '', $colprop, $ftype)));
$form[$category]['content'][] = $content;
}
}
$hiddenfields = new html_hiddenfield();
$hiddenfields->add(array('name' => '_adv', 'value' => 1));
$out = $RCMAIL->output->request_form(array(
'name' => 'form', 'method' => 'post',
'task' => $RCMAIL->task, 'action' => 'search',
'noclose' => true) + $attrib, $hiddenfields->show());
$RCMAIL->output->add_gui_object('editform', $attrib['id']);
unset($attrib['name']);
unset($attrib['id']);
foreach ($form as $f) {
if (!empty($f['content'])) {
$content = html::div('contactfieldgroup', join("\n", $f['content']));
$out .= html::tag('fieldset', $attrib,
html::tag('legend', null, Q($f['name']))
. $content) . "\n";
}
}
return $out . '</form>';
}
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index 032bafd1d..91543ec85 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -1,924 +1,936 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/settings/func.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2007, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Provide functionality for user's settings & preferences |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
if (!$OUTPUT->ajax_call)
$OUTPUT->set_pagetitle(rcube_label('preferences'));
// similar function as /steps/settings/identities.inc::rcmail_identity_frame()
function rcmail_preferences_frame($attrib)
{
global $OUTPUT;
if (!$attrib['id'])
$attrib['id'] = 'rcmprefsframe';
$attrib['name'] = $attrib['id'];
$OUTPUT->set_env('contentframe', $attrib['name']);
$OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/blank.gif');
return html::iframe($attrib);
}
function rcmail_sections_list($attrib)
{
global $RCMAIL;
// add id to message list table if not specified
if (!strlen($attrib['id']))
$attrib['id'] = 'rcmsectionslist';
list($list, $cols) = rcmail_user_prefs();
// create XHTML table
$out = rcube_table_output($attrib, $list, $cols, 'id');
// set client env
$RCMAIL->output->add_gui_object('sectionslist', $attrib['id']);
$RCMAIL->output->include_script('list.js');
return $out;
}
function rcmail_identities_list($attrib)
{
global $OUTPUT, $RCMAIL;
// add id to message list table if not specified
if (!strlen($attrib['id']))
$attrib['id'] = 'rcmIdentitiesList';
// get identities list and define 'mail' column
$list = $RCMAIL->user->list_identities();
foreach ($list as $idx => $row)
$list[$idx]['mail'] = trim($row['name'] . ' <' . rcube_idn_to_utf8($row['email']) .'>');
// get all identites from DB and define list of cols to be displayed
$plugin = $RCMAIL->plugins->exec_hook('identities_list', array(
'list' => $list,
'cols' => array('mail')));
// @TODO: use <UL> instead of <TABLE> for identities list
// create XHTML table
$out = rcube_table_output($attrib, $plugin['list'], $plugin['cols'], 'identity_id');
// set client env
$OUTPUT->add_gui_object('identitieslist', $attrib['id']);
return $out;
}
// similar function as in /steps/addressbook/edit.inc
function get_form_tags($attrib, $action, $id = null, $hidden = null)
{
global $EDIT_FORM, $RCMAIL;
$form_start = $form_end = '';
if (empty($EDIT_FORM)) {
$request_key = $action . (isset($id) ? '.'.$id : '');
$form_start = $RCMAIL->output->request_form(array(
'name' => 'form',
'method' => 'post',
'task' => $RCMAIL->task,
'action' => $action,
'request' => $request_key,
'noclose' => true
) + $attrib);
if (is_array($hidden)) {
$hiddenfields = new html_hiddenfield($hidden);
$form_start .= $hiddenfields->show();
}
$form_end = !strlen($attrib['form']) ? '</form>' : '';
$EDIT_FORM = !empty($attrib['form']) ? $attrib['form'] : 'form';
$RCMAIL->output->add_gui_object('editform', $EDIT_FORM);
}
return array($form_start, $form_end);
}
function rcmail_user_prefs($current=null)
{
global $RCMAIL;
$sections['general'] = array('id' => 'general', 'section' => rcube_label('uisettings'));
$sections['mailbox'] = array('id' => 'mailbox', 'section' => rcube_label('mailboxview'));
$sections['compose'] = array('id' => 'compose', 'section' => rcube_label('messagescomposition'));
$sections['mailview'] = array('id' => 'mailview','section' => rcube_label('messagesdisplaying'));
$sections['addressbook'] = array('id' => 'addressbook','section' => rcube_label('addressbook'));
$sections['folders'] = array('id' => 'folders', 'section' => rcube_label('specialfolders'));
$sections['server'] = array('id' => 'server', 'section' => rcube_label('serversettings'));
// hook + define list cols
$plugin = $RCMAIL->plugins->exec_hook('preferences_sections_list',
array('list' => $sections, 'cols' => array('section')));
$sections = $plugin['list'];
$config = $RCMAIL->config->all();
$no_override = array_flip($RCMAIL->config->get('dont_override', array()));
foreach ($sections as $idx => $sect) {
if ($current && $sect['id'] != $current)
continue;
$blocks = array();
switch ($sect['id']) {
// general
case 'general':
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
- 'list' => array('name' => Q(rcube_label('listoptions'))),
);
// language selection
if (!isset($no_override['language'])) {
$a_lang = $RCMAIL->list_languages();
asort($a_lang);
$field_id = 'rcmfd_lang';
$select_lang = new html_select(array('name' => '_language', 'id' => $field_id));
$select_lang->add(array_values($a_lang), array_keys($a_lang));
$blocks['main']['options']['language'] = array(
'title' => html::label($field_id, Q(rcube_label('language'))),
'content' => $select_lang->show($RCMAIL->user->language),
);
}
- // show page size selection
+ // timezone selection
if (!isset($no_override['timezone'])) {
$field_id = 'rcmfd_timezone';
$select_timezone = new html_select(array('name' => '_timezone', 'id' => $field_id,
'onchange' => "$('#rcmfd_dst').attr('disabled', this.selectedIndex==0)"));
$select_timezone->add(rcube_label('autodetect'), 'auto');
$select_timezone->add('(GMT -11:00) Midway Island, Samoa', '-11');
$select_timezone->add('(GMT -10:00) Hawaii', '-10');
$select_timezone->add('(GMT -9:30) Marquesas Islands', '-9.5');
$select_timezone->add('(GMT -9:00) Alaska', '-9');
$select_timezone->add('(GMT -8:00) Pacific Time (US/Canada)', '-8');
$select_timezone->add('(GMT -7:00) Mountain Time (US/Canada)', '-7');
$select_timezone->add('(GMT -6:00) Central Time (US/Canada), Mexico City', '-6');
$select_timezone->add('(GMT -5:00) Eastern Time (US/Canada), Bogota, Lima', '-5');
$select_timezone->add('(GMT -4:30) Caracas', '-4.5');
$select_timezone->add('(GMT -4:00) Atlantic Time (Canada), La Paz', '-4');
$select_timezone->add('(GMT -3:30) Nfld Time (Canada), Nfld, S. Labador', '-3.5');
$select_timezone->add('(GMT -3:00) Brazil, Buenos Aires, Georgetown', '-3');
$select_timezone->add('(GMT -2:00) Mid-Atlantic', '-2');
$select_timezone->add('(GMT -1:00) Azores, Cape Verde Islands', '-1');
$select_timezone->add('(GMT) Western Europe, London, Lisbon, Casablanca', '0');
$select_timezone->add('(GMT +1:00) Central European Time', '1');
$select_timezone->add('(GMT +2:00) EET: Tallinn, Helsinki, Kaliningrad, South Africa', '2');
$select_timezone->add('(GMT +3:00) Baghdad, Kuwait, Riyadh, Moscow, Nairobi', '3');
$select_timezone->add('(GMT +3:30) Tehran', '3.5');
$select_timezone->add('(GMT +4:00) Abu Dhabi, Muscat, Baku, Tbilisi', '4');
$select_timezone->add('(GMT +4:30) Kabul', '4.5');
$select_timezone->add('(GMT +5:00) Ekaterinburg, Islamabad, Karachi', '5');
$select_timezone->add('(GMT +5:30) Chennai, Kolkata, Mumbai, New Delhi', '5.5');
$select_timezone->add('(GMT +5:45) Kathmandu', '5.75');
$select_timezone->add('(GMT +6:00) Almaty, Dhaka, Colombo', '6');
$select_timezone->add('(GMT +6:30) Cocos Islands, Myanmar', '6.5');
$select_timezone->add('(GMT +7:00) Bangkok, Hanoi, Jakarta', '7');
$select_timezone->add('(GMT +8:00) Beijing, Perth, Singapore, Taipei', '8');
$select_timezone->add('(GMT +8:45) Caiguna, Eucla, Border Village', '8.75');
$select_timezone->add('(GMT +9:00) Tokyo, Seoul, Yakutsk', '9');
$select_timezone->add('(GMT +9:30) Adelaide, Darwin', '9.5');
$select_timezone->add('(GMT +10:00) EAST/AEST: Sydney, Guam, Vladivostok', '10');
$select_timezone->add('(GMT +10:30) New South Wales', '10.5');
$select_timezone->add('(GMT +11:00) Magadan, Solomon Islands', '11');
$select_timezone->add('(GMT +11:30) Norfolk Island', '11.5');
$select_timezone->add('(GMT +12:00) Auckland, Wellington, Kamchatka', '12');
$select_timezone->add('(GMT +12:45) Chatham Islands', '12.75');
$select_timezone->add('(GMT +13:00) Tonga, Pheonix Islands', '13');
$select_timezone->add('(GMT +14:00) Kiribati', '14');
$blocks['main']['options']['timezone'] = array(
'title' => html::label($field_id, Q(rcube_label('timezone'))),
'content' => $select_timezone->show((string)$config['timezone']),
);
}
// daylight savings
if (!isset($no_override['dst_active'])) {
$field_id = 'rcmfd_dst';
$input_dst = new html_checkbox(array('name' => '_dst_active', 'id' => $field_id, 'value' => 1, 'disabled' => ($config['timezone'] === 'auto')));
$blocks['main']['options']['dstactive'] = array(
'title' => html::label($field_id, Q(rcube_label('dstactive'))),
'content' => $input_dst->show($config['dst_active']),
);
}
// date/time formatting
if (!isset($no_override['time_format'])) {
$reftime = mktime(7,30,0);
$field_id = 'rcmfd_time_format';
$select_time = new html_select(array('name' => '_time_format', 'id' => $field_id));
foreach ((array)$RCMAIL->config->get('time_formats', array('G:i', 'H:i', 'g:i a', 'h:i A')) as $choice)
$select_time->add(date($choice, $reftime), $choice);
$blocks['main']['options']['time_format'] = array(
'title' => html::label($field_id, Q(rcube_label('timeformat'))),
'content' => $select_time->show($RCMAIL->config->get('time_format')),
);
}
if (!isset($no_override['date_format'])) {
$refdate = mktime(12,30,0,7,24);
$field_id = 'rcmfd_date_format';
$select_date = new html_select(array('name' => '_date_format', 'id' => $field_id));
foreach ((array)$RCMAIL->config->get('date_formats', array('Y-m-d','d-m-Y','Y/m/d','m/d/Y','d/m/Y','d.m.Y','j.n.Y')) as $choice)
$select_date->add(date($choice, $refdate), $choice);
$blocks['main']['options']['date_format'] = array(
'title' => html::label($field_id, Q(rcube_label('dateformat'))),
'content' => $select_date->show($config['date_format']),
);
}
// MM: Show checkbox for toggling 'pretty dates'
if (!isset($no_override['prettydate'])) {
$field_id = 'rcmfd_prettydate';
$input_prettydate = new html_checkbox(array('name' => '_pretty_date', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['prettydate'] = array(
'title' => html::label($field_id, Q(rcube_label('prettydate'))),
'content' => $input_prettydate->show($config['prettydate']?1:0),
);
}
- // show page size selection
- if (!isset($no_override['pagesize'])) {
- $field_id = 'rcmfd_pgsize';
- $input_pagesize = new html_inputfield(array('name' => '_pagesize', 'id' => $field_id, 'size' => 5));
-
- $blocks['list']['options']['pagesize'] = array(
- 'title' => html::label($field_id, Q(rcube_label('pagesize'))),
- 'content' => $input_pagesize->show($config['pagesize']),
- );
- }
-
// show drop-down for available skins
if (!isset($no_override['skin'])) {
$skins = rcmail_get_skins();
if (count($skins) > 1) {
$field_id = 'rcmfd_skin';
$input_skin = new html_select(array('name'=>'_skin', 'id'=>$field_id));
foreach($skins as $skin)
$input_skin->add($skin, $skin);
$blocks['main']['options']['skin'] = array(
'title' => html::label($field_id, Q(rcube_label('skin'))),
'content' => $input_skin->show($config['skin']),
);
}
}
break;
// Mailbox view (mail screen)
case 'mailbox':
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
'new_message' => array('name' => Q(rcube_label('newmessage'))),
);
// show config parameter for preview pane
if (!isset($no_override['preview_pane'])) {
$field_id = 'rcmfd_preview';
$input_preview = new html_checkbox(array('name' => '_preview_pane', 'id' => $field_id, 'value' => 1,
'onchange' => "$('#rcmfd_preview_pane_mark_read').prop('disabled', !this.checked)"));
$blocks['main']['options']['preview_pane'] = array(
'title' => html::label($field_id, Q(rcube_label('previewpane'))),
'content' => $input_preview->show($config['preview_pane']?1:0),
);
}
// show config parameter for preview pane auto mark as read delay
if (!isset($no_override['preview_pane_mark_read'])) {
// apply default if config option is not set at all
$config['preview_pane_mark_read'] = $RCMAIL->config->get('preview_pane_mark_read', 0);
$field_id = 'rcmfd_preview_pane_mark_read';
$select_delay = new html_select(array('name' => '_preview_pane_mark_read', 'id' => $field_id,
'disabled' => $config['preview_pane']?0:1));
$select_delay->add(rcube_label('never'), '-1');
$select_delay->add(rcube_label('immediately'), 0);
foreach(array(5, 10, 20, 30) as $sec)
$select_delay->add(rcube_label(array('name' => 'afternseconds', 'vars' => array('n' => $sec))), $sec);
$blocks['main']['options']['preview_pane_mark_read'] = array(
'title' => html::label($field_id, Q(rcube_label('previewpanemarkread'))),
'content' => $select_delay->show(intval($config['preview_pane_mark_read'])),
);
}
if (!isset($no_override['mdn_requests'])) {
$field_id = 'rcmfd_mdn_requests';
$select_mdn_requests = new html_select(array('name' => '_mdn_requests', 'id' => $field_id));
$select_mdn_requests->add(rcube_label('askuser'), 0);
$select_mdn_requests->add(rcube_label('autosend'), 1);
$select_mdn_requests->add(rcube_label('autosendknown'), 3);
$select_mdn_requests->add(rcube_label('autosendknownignore'), 4);
$select_mdn_requests->add(rcube_label('ignore'), 2);
$blocks['main']['options']['mdn_requests'] = array(
'title' => html::label($field_id, Q(rcube_label('mdnrequests'))),
'content' => $select_mdn_requests->show($config['mdn_requests']),
);
}
$RCMAIL->imap_connect();
$threading_supported = $RCMAIL->imap->get_capability('THREAD');
if (!isset($no_override['autoexpand_threads']) && $threading_supported) {
$field_id = 'rcmfd_autoexpand_threads';
$select_autoexpand_threads = new html_select(array('name' => '_autoexpand_threads', 'id' => $field_id));
$select_autoexpand_threads->add(rcube_label('never'), 0);
$select_autoexpand_threads->add(rcube_label('do_expand'), 1);
$select_autoexpand_threads->add(rcube_label('expand_only_unread'), 2);
$blocks['main']['options']['autoexpand_threads'] = array(
'title' => html::label($field_id, Q(rcube_label('autoexpand_threads'))),
'content' => $select_autoexpand_threads->show($config['autoexpand_threads']),
);
}
+ // show page size selection
+ if (!isset($no_override['pagesize'])) {
+ $field_id = 'rcmfd_pagesize';
+ $input_pagesize = new html_inputfield(array('name' => '_pagesize', 'id' => $field_id, 'size' => 5));
+
+ $blocks['main']['options']['pagesize'] = array(
+ 'title' => html::label($field_id, Q(rcube_label('pagesize'))),
+ 'content' => $input_pagesize->show($config['pagesize']),
+ );
+ }
+
if (!isset($no_override['keep_alive'])) {
$field_id = 'rcmfd_keep_alive';
$select_keep_alive = new html_select(array('name' => '_keep_alive', 'id' => $field_id));
foreach(array(1, 3, 5, 10, 15, 30, 60) as $min)
if((!$config['min_keep_alive'] || $config['min_keep_alive'] <= $min * 60)
&& (!$config['session_lifetime'] || $config['session_lifetime'] > $min)) {
$select_keep_alive->add(rcube_label(array('name' => 'everynminutes', 'vars' => array('n' => $min))), $min);
}
$blocks['new_message']['options']['keep_alive'] = array(
'title' => html::label($field_id, Q(rcube_label('keepalive'))),
'content' => $select_keep_alive->show($config['keep_alive']/60),
);
}
if (!isset($no_override['check_all_folders'])) {
$field_id = 'rcmfd_check_all_folders';
$input_check_all = new html_checkbox(array('name' => '_check_all_folders', 'id' => $field_id, 'value' => 1));
$blocks['new_message']['options']['check_all_folders'] = array(
'title' => html::label($field_id, Q(rcube_label('checkallfolders'))),
'content' => $input_check_all->show($config['check_all_folders']?1:0),
);
}
break;
// Message viewing
case 'mailview':
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
);
// show checkbox for HTML/plaintext messages
if (!isset($no_override['prefer_html'])) {
$field_id = 'rcmfd_htmlmsg';
$input_preferhtml = new html_checkbox(array('name' => '_prefer_html', 'id' => $field_id, 'value' => 1,
'onchange' => "$('#rcmfd_show_images').prop('disabled', !this.checked).val(0)"));
$blocks['main']['options']['prefer_html'] = array(
'title' => html::label($field_id, Q(rcube_label('preferhtml'))),
'content' => $input_preferhtml->show($config['prefer_html']?1:0),
);
}
if (!isset($no_override['default_charset'])) {
$field_id = 'rcmfd_default_charset';
$blocks['main']['options']['default_charset'] = array(
'title' => html::label($field_id, Q(rcube_label('defaultcharset'))),
'content' => $RCMAIL->output->charset_selector(array(
'name' => '_default_charset', 'selected' => $config['default_charset']
))
);
}
if (!isset($no_override['show_images'])) {
$field_id = 'rcmfd_show_images';
$input_show_images = new html_select(array('name' => '_show_images', 'id' => $field_id,
'disabled' => !$config['prefer_html']));
$input_show_images->add(rcube_label('never'), 0);
$input_show_images->add(rcube_label('fromknownsenders'), 1);
$input_show_images->add(rcube_label('always'), 2);
$blocks['main']['options']['show_images'] = array(
'title' => html::label($field_id, Q(rcube_label('showremoteimages'))),
'content' => $input_show_images->show($config['prefer_html'] ? $config['show_images'] : 0),
);
}
if (!isset($no_override['inline_images'])) {
$field_id = 'rcmfd_inline_images';
$input_inline_images = new html_checkbox(array('name' => '_inline_images', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['inline_images'] = array(
'title' => html::label($field_id, Q(rcube_label('showinlineimages'))),
'content' => $input_inline_images->show($config['inline_images']?1:0),
);
}
// "display after delete" checkbox
if (!isset($no_override['display_next'])) {
$field_id = 'rcmfd_displaynext';
$input_displaynext = new html_checkbox(array('name' => '_display_next', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['display_next'] = array(
'title' => html::label($field_id, Q(rcube_label('displaynext'))),
'content' => $input_displaynext->show($config['display_next']?1:0),
);
}
break;
// Mail composition
case 'compose':
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
'spellcheck' => array('name' => Q(rcube_label('spellcheckoptions'))),
'sig' => array('name' => Q(rcube_label('signatureoptions'))),
);
if (!isset($no_override['htmleditor'])) {
$field_id = 'rcmfd_htmleditor';
$select_htmleditor = new html_select(array('name' => '_htmleditor', 'id' => $field_id));
$select_htmleditor->add(rcube_label('never'), 0);
$select_htmleditor->add(rcube_label('always'), 1);
$select_htmleditor->add(rcube_label('htmlonreply'), 2);
$blocks['main']['options']['htmleditor'] = array(
'title' => html::label($field_id, Q(rcube_label('htmleditor'))),
'content' => $select_htmleditor->show(intval($config['htmleditor'])),
);
}
if (!isset($no_override['draft_autosave'])) {
$field_id = 'rcmfd_autosave';
$select_autosave = new html_select(array('name' => '_draft_autosave', 'id' => $field_id, 'disabled' => empty($config['drafts_mbox'])));
$select_autosave->add(rcube_label('never'), 0);
foreach (array(1, 3, 5, 10) as $i => $min)
$select_autosave->add(rcube_label(array('name' => 'everynminutes', 'vars' => array('n' => $min))), $min*60);
$blocks['main']['options']['draft_autosave'] = array(
'title' => html::label($field_id, Q(rcube_label('autosavedraft'))),
'content' => $select_autosave->show($config['draft_autosave']),
);
}
if (!isset($no_override['mime_param_folding'])) {
$field_id = 'rcmfd_param_folding';
$select_param_folding = new html_select(array('name' => '_mime_param_folding', 'id' => $field_id));
$select_param_folding->add(rcube_label('2231folding'), 0);
$select_param_folding->add(rcube_label('miscfolding'), 1);
$select_param_folding->add(rcube_label('2047folding'), 2);
$blocks['main']['options']['mime_param_folding'] = array(
'advanced' => true,
'title' => html::label($field_id, Q(rcube_label('mimeparamfolding'))),
'content' => $select_param_folding->show($config['mime_param_folding']),
);
}
if (!isset($no_override['force_7bit'])) {
$field_id = 'rcmfd_force_7bit';
$input_7bit = new html_checkbox(array('name' => '_force_7bit', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['force_7bit'] = array(
'title' => html::label($field_id, Q(rcube_label('force7bit'))),
'content' => $input_7bit->show($config['force_7bit']?1:0),
);
}
if (!isset($no_override['mdn_default'])) {
$field_id = 'rcmfd_mdn_default';
$input_mdn = new html_checkbox(array('name' => '_mdn_default', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['mdn_default'] = array(
'title' => html::label($field_id, Q(rcube_label('reqmdn'))),
'content' => $input_mdn->show($config['mdn_default']?1:0),
);
}
if (!isset($no_override['dsn_default'])) {
$field_id = 'rcmfd_dsn_default';
$input_dsn = new html_checkbox(array('name' => '_dsn_default', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['dsn_default'] = array(
'title' => html::label($field_id, Q(rcube_label('reqdsn'))),
'content' => $input_dsn->show($config['dsn_default']?1:0),
);
}
if (!isset($no_override['reply_same_folder'])) {
$field_id = 'rcmfd_reply_same_folder';
$input_reply_same_folder = new html_checkbox(array('name' => '_reply_same_folder', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['reply_same_folder'] = array(
'title' => html::label($field_id, Q(rcube_label('replysamefolder'))),
'content' => $input_reply_same_folder->show($config['reply_same_folder']?1:0),
);
}
if (!isset($no_override['top_posting'])) {
$field_id = 'rcmfd_top_posting';
$select_replymode = new html_select(array('name' => '_top_posting', 'id' => $field_id,
'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex==0)"));
$select_replymode->add(rcube_label('replybottomposting'), 0);
$select_replymode->add(rcube_label('replytopposting'), 1);
$blocks['main']['options']['top_posting'] = array(
'title' => html::label($field_id, Q(rcube_label('whenreplying'))),
'content' => $select_replymode->show($config['top_posting']?1:0),
);
}
if (!isset($no_override['spellcheck_before_send']) && $config['enable_spellcheck']) {
$field_id = 'rcmfd_spellcheck_before_send';
$input_spellcheck = new html_checkbox(array('name' => '_spellcheck_before_send', 'id' => $field_id, 'value' => 1));
$blocks['spellcheck']['options']['spellcheck_before_send'] = array(
'title' => html::label($field_id, Q(rcube_label('spellcheckbeforesend'))),
'content' => $input_spellcheck->show($config['spellcheck_before_send']?1:0),
);
}
if ($config['enable_spellcheck']) {
foreach (array('syms', 'nums', 'caps') as $key) {
$key = 'spellcheck_ignore_'.$key;
if (!isset($no_override[$key])) {
$input_spellcheck = new html_checkbox(array('name' => '_'.$key, 'id' => 'rcmfd_'.$key, 'value' => 1));
$blocks['spellcheck']['options'][$key] = array(
'title' => html::label($field_id, Q(rcube_label(str_replace('_', '', $key)))),
'content' => $input_spellcheck->show($config[$key]?1:0),
);
}
}
}
if (!isset($no_override['show_sig'])) {
$field_id = 'rcmfd_show_sig';
$select_show_sig = new html_select(array('name' => '_show_sig', 'id' => $field_id));
$select_show_sig->add(rcube_label('never'), 0);
$select_show_sig->add(rcube_label('always'), 1);
$select_show_sig->add(rcube_label('newmessageonly'), 2);
$select_show_sig->add(rcube_label('replyandforwardonly'), 3);
$blocks['sig']['options']['show_sig'] = array(
'title' => html::label($field_id, Q(rcube_label('autoaddsignature'))),
'content' => $select_show_sig->show($RCMAIL->config->get('show_sig', 1)),
);
}
if (!isset($no_override['sig_above'])) {
$field_id = 'rcmfd_sig_above';
$select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => !$config['top_posting']));
$select_sigabove->add(rcube_label('belowquote'), 0);
$select_sigabove->add(rcube_label('abovequote'), 1);
$blocks['sig']['options']['sig_above'] = array(
'title' => html::label($field_id, Q(rcube_label('replysignaturepos'))),
'content' => $select_sigabove->show($config['sig_above']?1:0),
);
}
if (!isset($no_override['strip_existing_sig'])) {
$field_id = 'rcmfd_strip_existing_sig';
$input_stripexistingsig = new html_checkbox(array('name' => '_strip_existing_sig', 'id' => $field_id, 'value' => 1));
$blocks['sig']['options']['strip_existing_sig'] = array(
'title' => html::label($field_id, Q(rcube_label('replyremovesignature'))),
'content' => $input_stripexistingsig->show($config['strip_existing_sig']?1:0),
);
}
if (!isset($no_override['default_font'])) {
$field_id = 'rcmfd_default_font';
$fonts = rcube_fontdefs();
$default_font = $config['default_font'] ? $config['default_font'] : 'Verdana';
$select = '<select name="_default_font" id="'.$field_id.'">';
foreach ($fonts as $fname => $font)
$select .= '<option value="'.$fname.'"'
. ($fname == $default_font ? ' selected="selected"' : '')
. ' style=\'font-family: ' . $font . '\'>'
. Q($fname) . '</option>';
$select .= '</select>';
$blocks['main']['options']['default_font'] = array(
'title' => html::label($field_id, Q(rcube_label('defaultfont'))),
'content' => $select
);
}
break;
// Addressbook config
case 'addressbook':
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
);
if (!isset($no_override['default_addressbook'])
&& ($books = $RCMAIL->get_address_sources(true))
) {
$field_id = 'rcmfd_default_addressbook';
$select_abook = new html_select(array('name' => '_default_addressbook', 'id' => $field_id));
foreach ($books as $book) {
$select_abook->add($book['name'], $book['id']);
}
$blocks['main']['options']['default_addressbook'] = array(
'title' => html::label($field_id, Q(rcube_label('defaultaddressbook'))),
'content' => $select_abook->show($config['default_addressbook']),
);
}
if (!isset($no_override['autocomplete_single'])) {
$field_id = 'rcmfd_autocomplete_single';
$checkbox = new html_checkbox(array('name' => '_autocomplete_single', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['autocomplete_single'] = array(
'title' => html::label($field_id, Q(rcube_label('autocompletesingle'))),
'content' => $checkbox->show($config['autocomplete_single']?1:0),
);
}
+ // show addressbook page size selection
+ if (!isset($no_override['addressbook_pagesize'])) {
+ $field_id = 'rcmfd_addressbook_pagesize';
+ $input_pagesize = new html_inputfield(array('name' => '_addressbook_pagesize', 'id' => $field_id, 'size' => 5));
+
+ $size = $config['addressbook_pagesize'] ? $config['addressbook_pagesize'] : $config['pagesize'];
+
+ $blocks['main']['options']['pagesize'] = array(
+ 'title' => html::label($field_id, Q(rcube_label('pagesize'))),
+ 'content' => $input_pagesize->show((int)$size),
+ );
+ }
+
break;
// Special IMAP folders
case 'folders':
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
);
// Configure special folders
if (!isset($no_override['default_imap_folders'])) {
$RCMAIL->imap_connect();
// load folders list only when needed
if ($current) {
$select = rcmail_mailbox_select(array(
'noselection' => '---',
'realnames' => true,
'maxlength' => 30,
'folder_filter' => 'mail',
'folder_rights' => 'w',
// #1486114, #1488279
'onchange' => "if ($(this).val() == 'INBOX') $(this).val('')",
));
}
else // dummy select
$select = new html_select();
if (!isset($no_override['drafts_mbox']))
$blocks['main']['options']['drafts_mbox'] = array(
'title' => Q(rcube_label('drafts')),
'content' => $select->show($config['drafts_mbox'], array('name' => "_drafts_mbox")),
);
if (!isset($no_override['sent_mbox']))
$blocks['main']['options']['sent_mbox'] = array(
'title' => Q(rcube_label('sent')),
'content' => $select->show($config['sent_mbox'], array('name' => "_sent_mbox")),
);
if (!isset($no_override['junk_mbox']))
$blocks['main']['options']['junk_mbox'] = array(
'title' => Q(rcube_label('junk')),
'content' => $select->show($config['junk_mbox'], array('name' => "_junk_mbox")),
);
if (!isset($no_override['trash_mbox']))
$blocks['main']['options']['trash_mbox'] = array(
'title' => Q(rcube_label('trash')),
'content' => $select->show($config['trash_mbox'], array('name' => "_trash_mbox")),
);
}
break;
// Server settings
case 'server':
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
'maintenance' => array('name' => Q(rcube_label('maintenance'))),
);
if (!isset($no_override['read_when_deleted'])) {
$field_id = 'rcmfd_read_deleted';
$input_readdeleted = new html_checkbox(array('name' => '_read_when_deleted', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['read_when_deleted'] = array(
'title' => html::label($field_id, Q(rcube_label('readwhendeleted'))),
'content' => $input_readdeleted->show($config['read_when_deleted']?1:0),
);
}
if (!isset($no_override['flag_for_deletion'])) {
$field_id = 'rcmfd_flag_for_deletion';
$input_flagfordeletion = new html_checkbox(array('name' => '_flag_for_deletion', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['flag_for_deletion'] = array(
'title' => html::label($field_id, Q(rcube_label('flagfordeletion'))),
'content' => $input_flagfordeletion->show($config['flag_for_deletion']?1:0),
);
}
// don't show deleted messages
if (!isset($no_override['skip_deleted'])) {
$field_id = 'rcmfd_skip_deleted';
$input_purge = new html_checkbox(array('name' => '_skip_deleted', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['skip_deleted'] = array(
'title' => html::label($field_id, Q(rcube_label('skipdeleted'))),
'content' => $input_purge->show($config['skip_deleted']?1:0),
);
}
if (!isset($no_override['delete_always'])) {
$field_id = 'rcmfd_delete_always';
$input_delete_always = new html_checkbox(array('name' => '_delete_always', 'id' => $field_id, 'value' => 1));
$blocks['main']['options']['delete_always'] = array(
'title' => html::label($field_id, Q(rcube_label('deletealways'))),
'content' => $input_delete_always->show($config['delete_always']?1:0),
);
}
// Trash purging on logout
if (!isset($no_override['logout_purge'])) {
$field_id = 'rcmfd_logout_purge';
$input_purge = new html_checkbox(array('name' => '_logout_purge', 'id' => $field_id, 'value' => 1));
$blocks['maintenance']['options']['logout_purge'] = array(
'title' => html::label($field_id, Q(rcube_label('logoutclear'))),
'content' => $input_purge->show($config['logout_purge']?1:0),
);
}
// INBOX compacting on logout
if (!isset($no_override['logout_expunge'])) {
$field_id = 'rcmfd_logout_expunge';
$input_expunge = new html_checkbox(array('name' => '_logout_expunge', 'id' => $field_id, 'value' => 1));
$blocks['maintenance']['options']['logout_expunge'] = array(
'title' => html::label($field_id, Q(rcube_label('logoutcompact'))),
'content' => $input_expunge->show($config['logout_expunge']?1:0),
);
}
break;
}
$data = $RCMAIL->plugins->exec_hook('preferences_list', array('section' => $sect['id'], 'blocks' => $blocks));
$found = false;
// create output
foreach ($data['blocks'] as $block) {
if (!empty($block['content']) || !empty($block['options'])) {
$found = true;
break;
}
}
if (!$found)
unset($sections[$idx]);
else
$sections[$idx]['blocks'] = $data['blocks'];
}
return array($sections, $plugin['cols']);
}
function rcmail_get_skins()
{
$path = 'skins';
$skins = array();
$dir = opendir($path);
if (!$dir)
return false;
while (($file = readdir($dir)) !== false)
{
$filename = $path.'/'.$file;
if (!preg_match('/^\./', $file) && is_dir($filename) && is_readable($filename))
$skins[] = $file;
}
closedir($dir);
return $skins;
}
function rcmail_folder_options($mailbox)
{
global $RCMAIL;
$options = $RCMAIL->imap->mailbox_info($mailbox);
$options['protected'] = $options['is_root'] || ($options['special'] && $RCMAIL->config->get('protect_default_folders'));
return $options;
}
/**
* Updates (or creates) folder row in the subscriptions table
*
* @param string $name Folder name
* @param string $oldname Old folder name (for update)
* @param bool $subscribe Checks subscription checkbox
* @param string $class CSS class name for folder row
*/
function rcmail_update_folder_row($name, $oldname=null, $subscribe=false, $class_name=null)
{
global $RCMAIL, $CONFIG, $OUTPUT;
$delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
$name_utf8 = rcube_charset_convert($name, 'UTF7-IMAP');
$protected = ($CONFIG['protect_default_folders'] == true && in_array($name, $CONFIG['default_imap_folders']));
$foldersplit = explode($delimiter, $RCMAIL->imap->mod_mailbox($name));
$level = count($foldersplit) - 1;
$display_name = str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $level)
. Q($protected ? rcmail_localize_foldername($name) : rcube_charset_convert($foldersplit[$level], 'UTF7-IMAP'));
if ($oldname === null)
$OUTPUT->command('add_folder_row', $name_utf8, $display_name, $protected, $subscribe,
false, $class_name);
else
$OUTPUT->command('replace_folder_row', rcube_charset_convert($oldname, 'UTF7-IMAP'),
$name_utf8, $display_name, $protected, $class_name);
}
// register UI objects
$OUTPUT->add_handlers(array(
'prefsframe' => 'rcmail_preferences_frame',
'sectionslist' => 'rcmail_sections_list',
'identitieslist' => 'rcmail_identities_list',
));
// register action aliases
$RCMAIL->register_action_map(array(
'folders' => 'folders.inc',
'rename-folder' => 'folders.inc',
'delete-folder' => 'folders.inc',
'subscribe' => 'folders.inc',
'unsubscribe' => 'folders.inc',
'purge' => 'folders.inc',
'folder-size' => 'folders.inc',
'add-identity' => 'edit_identity.inc',
));
diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc
index 5cb79d160..2e89ce1c0 100644
--- a/program/steps/settings/save_prefs.inc
+++ b/program/steps/settings/save_prefs.inc
@@ -1,202 +1,207 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/settings/save_prefs.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2009, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Save user preferences to DB and to the current session |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
$CURR_SECTION = get_input_value('_section', RCUBE_INPUT_POST);
$a_user_prefs = array();
// set options for specified section
switch ($CURR_SECTION)
{
case 'general':
$a_user_prefs = array(
'language' => isset($_POST['_language']) ? get_input_value('_language', RCUBE_INPUT_POST) : $CONFIG['language'],
'timezone' => isset($_POST['_timezone']) ? (is_numeric($_POST['_timezone']) ? floatval($_POST['_timezone']) : get_input_value('_timezone', RCUBE_INPUT_POST)) : $CONFIG['timezone'],
'dst_active' => isset($_POST['_dst_active']) ? TRUE : FALSE,
- 'pagesize' => is_numeric($_POST['_pagesize']) ? max(2, intval($_POST['_pagesize'])) : $CONFIG['pagesize'],
'date_format' => isset($_POST['_date_format']) ? get_input_value('_date_format', RCUBE_INPUT_POST) : $CONFIG['date_format'],
'time_format' => isset($_POST['_time_format']) ? get_input_value('_time_format', RCUBE_INPUT_POST) : ($CONFIG['time_format'] ? $CONFIG['time_format'] : 'H:i'),
'prettydate' => isset($_POST['_pretty_date']) ? TRUE : FALSE,
'skin' => isset($_POST['_skin']) ? get_input_value('_skin', RCUBE_INPUT_POST) : $CONFIG['skin'],
);
-
+
// compose derived date/time format strings
if ((isset($_POST['_date_format']) || isset($_POST['_time_format'])) && $a_user_prefs['date_format'] && $a_user_prefs['time_format']) {
$a_user_prefs['date_short'] = 'D ' . $a_user_prefs['time_format'];
$a_user_prefs['date_long'] = $a_user_prefs['date_format'] . ' ' . $a_user_prefs['time_format'];
}
break;
case 'mailbox':
$a_user_prefs = array(
'preview_pane' => isset($_POST['_preview_pane']) ? TRUE : FALSE,
'preview_pane_mark_read' => isset($_POST['_preview_pane_mark_read']) ? intval($_POST['_preview_pane_mark_read']) : $CONFIG['preview_pane_mark_read'],
'autoexpand_threads' => isset($_POST['_autoexpand_threads']) ? intval($_POST['_autoexpand_threads']) : 0,
'mdn_requests' => isset($_POST['_mdn_requests']) ? intval($_POST['_mdn_requests']) : 0,
'keep_alive' => isset($_POST['_keep_alive']) ? intval($_POST['_keep_alive'])*60 : $CONFIG['keep_alive'],
'check_all_folders' => isset($_POST['_check_all_folders']) ? TRUE : FALSE,
+ 'mail_pagesize' => is_numeric($_POST['_mail_pagesize']) ? max(2, intval($_POST['_mail_pagesize'])) : $CONFIG['mail_pagesize'],
);
break;
case 'mailview':
$a_user_prefs = array(
'prefer_html' => isset($_POST['_prefer_html']) ? TRUE : FALSE,
'inline_images' => isset($_POST['_inline_images']) ? TRUE : FALSE,
'show_images' => isset($_POST['_show_images']) ? intval($_POST['_show_images']) : 0,
'display_next' => isset($_POST['_display_next']) ? TRUE : FALSE,
'default_charset' => get_input_value('_default_charset', RCUBE_INPUT_POST),
);
break;
case 'compose':
$a_user_prefs = array(
'htmleditor' => intval($_POST['_htmleditor']),
'draft_autosave' => isset($_POST['_draft_autosave']) ? intval($_POST['_draft_autosave']) : 0,
'mime_param_folding' => isset($_POST['_mime_param_folding']) ? intval($_POST['_mime_param_folding']) : 0,
'force_7bit' => isset($_POST['_force_7bit']) ? TRUE : FALSE,
'mdn_default' => isset($_POST['_mdn_default']) ? TRUE : FALSE,
'dsn_default' => isset($_POST['_dsn_default']) ? TRUE : FALSE,
'reply_same_folder' => isset($_POST['_reply_same_folder']) ? TRUE : FALSE,
'spellcheck_before_send' => isset($_POST['_spellcheck_before_send']) ? TRUE : FALSE,
'spellcheck_ignore_syms' => isset($_POST['_spellcheck_ignore_syms']) ? TRUE : FALSE,
'spellcheck_ignore_nums' => isset($_POST['_spellcheck_ignore_nums']) ? TRUE : FALSE,
'spellcheck_ignore_caps' => isset($_POST['_spellcheck_ignore_caps']) ? TRUE : FALSE,
'show_sig' => isset($_POST['_show_sig']) ? intval($_POST['_show_sig']) : 1,
'top_posting' => !empty($_POST['_top_posting']),
'strip_existing_sig' => isset($_POST['_strip_existing_sig']),
'sig_above' => !empty($_POST['_sig_above']) && !empty($_POST['_top_posting']),
'default_font' => get_input_value('_default_font', RCUBE_INPUT_POST),
);
break;
case 'addressbook':
$a_user_prefs = array(
- 'default_addressbook' => get_input_value('_default_addressbook', RCUBE_INPUT_POST, true),
- 'autocomplete_single' => isset($_POST['_autocomplete_single']) ? TRUE : FALSE,
+ 'default_addressbook' => get_input_value('_default_addressbook', RCUBE_INPUT_POST, true),
+ 'autocomplete_single' => isset($_POST['_autocomplete_single']) ? TRUE : FALSE,
+ 'addressbook_pagesize' => is_numeric($_POST['_addressbook_pagesize']) ? max(2, intval($_POST['_addressbook_pagesize'])) : $CONFIG['addressbook_pagesize'],
);
break;
case 'server':
$a_user_prefs = array(
'read_when_deleted' => isset($_POST['_read_when_deleted']) ? TRUE : FALSE,
'skip_deleted' => isset($_POST['_skip_deleted']) ? TRUE : FALSE,
'flag_for_deletion' => isset($_POST['_flag_for_deletion']) ? TRUE : FALSE,
'delete_always' => isset($_POST['_delete_always']) ? TRUE : FALSE,
'logout_purge' => isset($_POST['_logout_purge']) ? TRUE : FALSE,
'logout_expunge' => isset($_POST['_logout_expunge']) ? TRUE : FALSE,
);
break;
case 'folders':
$a_user_prefs = array(
'drafts_mbox' => get_input_value('_drafts_mbox', RCUBE_INPUT_POST, true),
'sent_mbox' => get_input_value('_sent_mbox', RCUBE_INPUT_POST, true),
'junk_mbox' => get_input_value('_junk_mbox', RCUBE_INPUT_POST, true),
'trash_mbox' => get_input_value('_trash_mbox', RCUBE_INPUT_POST, true),
);
break;
}
$plugin = rcmail::get_instance()->plugins->exec_hook('preferences_save',
array('prefs' => $a_user_prefs, 'section' => $CURR_SECTION));
$a_user_prefs = $plugin['prefs'];
// don't override these parameters
foreach ((array)$CONFIG['dont_override'] as $p)
$a_user_prefs[$p] = $CONFIG[$p];
// verify some options
switch ($CURR_SECTION)
{
case 'general':
// switch UI language
if (isset($_POST['_language']) && $a_user_prefs['language'] != $_SESSION['language']) {
$RCMAIL->load_language($a_user_prefs['language']);
$OUTPUT->command('reload', 500);
}
// switch skin (if valid, otherwise unset the pref and fall back to default)
if (!$OUTPUT->set_skin($a_user_prefs['skin']))
unset($a_user_prefs['skin']);
else if ($RCMAIL->config->get('skin') != $a_user_prefs['skin'])
$OUTPUT->command('reload', 500);
// force min size
- if ($a_user_prefs['pagesize'] < 1)
- $a_user_prefs['pagesize'] = 10;
-
- if (isset($CONFIG['max_pagesize']) && ($a_user_prefs['pagesize'] > $CONFIG['max_pagesize']))
- $a_user_prefs['pagesize'] = (int) $CONFIG['max_pagesize'];
+ if ($a_user_prefs['mail_pagesize'] < 1)
+ $a_user_prefs['mail_pagesize'] = 10;
+ if ($a_user_prefs['addressbook_pagesize'] < 1)
+ $a_user_prefs['addressbook_pagesize'] = 10;
+
+ if (isset($CONFIG['max_pagesize']) && ($a_user_prefs['mail_pagesize'] > $CONFIG['max_pagesize']))
+ $a_user_prefs['mail_pagesize'] = (int) $CONFIG['max_pagesize'];
+ if (isset($CONFIG['max_pagesize']) && ($a_user_prefs['addressbook_pagesize'] > $CONFIG['max_pagesize']))
+ $a_user_prefs['addressbook_pagesize'] = (int) $CONFIG['max_pagesize'];
$a_user_prefs['timezone'] = (string) $a_user_prefs['timezone'];
break;
case 'mailbox':
// force keep_alive
if (isset($a_user_prefs['keep_alive'])) {
$a_user_prefs['keep_alive'] = max(60, $CONFIG['min_keep_alive'], $a_user_prefs['keep_alive']);
if (!empty($CONFIG['session_lifetime']))
$a_user_prefs['keep_alive'] = min($CONFIG['session_lifetime']*60, $a_user_prefs['keep_alive']);
}
break;
case 'folders':
// special handling for 'default_imap_folders'
if (in_array('default_imap_folders', (array)$CONFIG['dont_override'])) {
foreach (array('drafts_mbox','sent_mbox','junk_mbox','trash_mbox') as $p)
$a_user_prefs[$p] = $CONFIG[$p];
} else {
$a_user_prefs['default_imap_folders'] = array('INBOX');
foreach (array('drafts_mbox','sent_mbox','junk_mbox','trash_mbox') as $p) {
if ($a_user_prefs[$p])
$a_user_prefs['default_imap_folders'][] = $a_user_prefs[$p];
}
}
break;
}
// Save preferences
if (!$plugin['abort'])
$saved = $RCMAIL->user->save_prefs($a_user_prefs);
else
$saved = $plugin['result'];
if ($saved)
$OUTPUT->show_message('successfullysaved', 'confirmation');
else
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error');
// display the form again
rcmail_overwrite_action('edit-prefs');

File Metadata

Mime Type
text/x-diff
Expires
Tue, Aug 18, 8:07 AM (14 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1262457
Default Alt Text
(521 KB)

Event Timeline