From 2ea274eddb56da98fc5af2bcf21f01dc8dda0970 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Mon, 30 Mar 2009 22:41:36 -0600 Subject: [PATCH] New message flagging code. Framework is in place to allow user-defined flags/keywords/labels/whatever you want to call them. All code is present except for actual viewing of the user-defined flags. Still working out the UI - I think it is going to end up looking like the message labeling as seen in Gmail - trying to allow custom icons is way beyond the scope of this feature. But the flags are correctly set on the messages. This implements (most) of Ticket #937. --- imp/config/conf.xml | 18 +- imp/config/hooks.php.dist | 159 ++++------- imp/config/prefs.php.dist | 171 ++++++++++- imp/docs/CHANGES | 4 + imp/docs/RFCS | 1 + imp/docs/UPGRADING | 6 +- imp/js/src/ContextSensitive.js | 55 ++-- imp/js/src/DimpBase.js | 349 +++++++++++------------ imp/js/src/DimpCore.js | 4 +- imp/js/src/ViewPort.js | 55 +--- imp/js/src/compose-dimp.js | 11 +- imp/js/src/flagmanagement.js | 78 +++++ imp/js/src/fullmessage-dimp.js | 12 +- imp/js/src/mailbox.js | 272 +++++++----------- imp/js/src/message.js | 7 +- imp/lib/DIMP.php | 19 +- imp/lib/IMP.php | 14 +- imp/lib/Imap/Flags.php | 360 ++++++++++++++++++++++++ imp/lib/Mailbox.php | 68 +++-- imp/lib/UI/Mailbox.php | 23 +- imp/lib/Views/ListMessages.php | 82 ++---- imp/lib/Views/ShowMessage.php | 11 +- imp/lib/prefs.php | 47 ++++ imp/mailbox-mimp.php | 49 ++-- imp/mailbox.php | 174 +++++------- imp/message-mimp.php | 45 ++- imp/message.php | 66 ++--- imp/rss.php | 2 +- imp/templates/index/index-dimp.inc | 36 ++- imp/templates/javascript/mailbox-dimp.js | 1 - imp/templates/javascript_defs.php | 1 - imp/templates/mailbox/mailbox.html | 4 +- imp/templates/mailbox/navbar.html | 57 ++-- imp/templates/message/navbar_navigate.html | 28 +- imp/templates/message/navbar_top.html | 7 +- imp/templates/prefs/flagmanagement.inc | 50 ++++ imp/themes/graphics/mail_notanswered.png | Bin 308 -> 0 bytes imp/themes/graphics/mail_notdraft.png | Bin 376 -> 0 bytes imp/themes/screen-dimp.css | 98 +------ imp/themes/screen.css | 75 +++-- imp/themes/silver/graphics/mail_notanswered.png | Bin 439 -> 0 bytes imp/themes/silver/graphics/mail_notdraft.png | Bin 425 -> 0 bytes imp/themes/silver/screen-dimp.css | 39 +-- imp/themes/silver/screen.css | 37 +++ 44 files changed, 1507 insertions(+), 1088 deletions(-) create mode 100644 imp/js/src/flagmanagement.js create mode 100644 imp/lib/Imap/Flags.php create mode 100644 imp/templates/prefs/flagmanagement.inc delete mode 100644 imp/themes/graphics/mail_notanswered.png delete mode 100644 imp/themes/graphics/mail_notdraft.png delete mode 100644 imp/themes/silver/graphics/mail_notanswered.png delete mode 100644 imp/themes/silver/graphics/mail_notdraft.png diff --git a/imp/config/conf.xml b/imp/config/conf.xml index f6d2c4923..95cb2e0f9 100644 --- a/imp/config/conf.xml +++ b/imp/config/conf.xml @@ -369,6 +369,10 @@ a custom function to dynamically determine the icons shown for standard folders on the folder page? If so, make sure you define _imp_hook_mbox_icons() in hooks.php.">false + false false - false + false - false _("High Priority"))); -// } -// $tmp['class'][] = 'important'; -// break; -// -// case 'low': -// if ($mode == 'imp') { -// $tmp['status'] .= Horde::img('mail_priority_low.png', _("Low Priority"), array('title' => _("Low Priority"))); -// } -// $tmp['class'][] = 'unimportant'; -// break; -// } -// -// if (!empty($tmp)) { -// $ret[$uid] = $tmp; -// } -// } -// -// return $ret; +// return $flags; // } -// } // This is an example hook function for the IMP redirection scheme. This // function is called when the user opens a mailbox in IMP, and allows the @@ -368,6 +309,36 @@ // } // } +// This is an example hook function for the mailbox view. This functions +// allows additional information to be added/edited from the data that is +// passed to the mailbox display template: +// imp: TODO +// dimp: imp/templates/javascript/mailbox-dimp.js. +// The current entry array is passed in, the value returned should be the +// altered array to use in the template. If you are going to add new columns, +// you also have to update these fields: +// imp: TODO +// dimp: imp/templates/index/dimp.inc to contain the new field in the header +// imp/themes/screen-dimp.css to specify the column width. + +// if (!function_exists('_imp_hook_mailboxarray')) { +// function _imp_hook_mailboxarray($msgs, $view) { +// switch ($view) { +// case 'dimp': +// foreach (array_keys($msgs) as $key) { +// $msgs[$key]['foo'] = true; +// } +// break; +// +// case 'imp': +// // TODO +// break; +// } +// +// return $msg; +// } +// } + // This is an example hook function to disable composing messages. If the hook // returns true, message composition will be disabled. @@ -533,26 +504,6 @@ if (!function_exists('_imp_hook_quota')) { } } - -// This is an example hook function for the dynamic (dimp) mailbox view. This -// function is allows additional information to be added to the array that is -// is passed to the mailbox display template - -// imp/templates/javascript/mailbox-dimp.js. The current entry array is -// passed in, the value returned should be the altered array to use in the -// template. If you are going to add new columns, you also have to update -// imp/templates/index/dimp.inc to contain the new field in the header and -// imp/themes/screen-dimp.css to specify the column width. - -// if (!function_exists('_imp_hook_dimp_mailboxarray')) { -// function _imp_hook_dimp_mailboxarray($msgs) { -// foreach (array_keys($msgs) as $key) { -// $msgs[$key]['foo'] = true; -// } -// -// return $msg; -// } -// } - // This is an example hook function for the dynamic (dimp) message view. This // function allows additional information to be added to the array that is // passed to the message text display template - diff --git a/imp/config/prefs.php.dist b/imp/config/prefs.php.dist index 3b6a7ba7a..6755301a3 100644 --- a/imp/config/prefs.php.dist +++ b/imp/config/prefs.php.dist @@ -133,6 +133,13 @@ if (!empty($GLOBALS['conf']['mailbox']['show_preview'])) { } if (!$is_pop3) { + $prefGroups['flags'] = array( + 'column' => _("Message Options"), + 'label' => _("Message Flags"), + 'desc' => _("Customize flag highlighting."), + 'members' => array('flagmanagement') + ); + $prefGroups['fetchmail'] = array( 'column' => _("Message Options"), 'label' => _("Fetch Mail"), @@ -146,7 +153,7 @@ $prefGroups['display'] = array( 'label' => _("Mailbox and Folder Display Options"), 'desc' => _("Change display options such as how many messages you see on each page and how messages are sorted."), 'members' => array('mailbox_start', 'sortby', 'sortdir', 'max_msgs', - 'from_link', 'time_format') + 'from_link', 'time_format', 'atc_flag') ); if (!$is_pop3) { $prefGroups['display']['members'] = array_merge( @@ -527,6 +534,7 @@ $_prefs['stationery_link'] = array( 'desc' => _("To the stationery and form responses.")); $_prefs['stationery'] = array( + // value = serialize(array()) 'value' => 'a:0:{}', 'locked' => false, 'shared' => false, @@ -1068,6 +1076,154 @@ $_prefs['preview_show_tooltip'] = array( // End Message Preview +// IMAP Flag preferences + +// UI for flag management. +$_prefs['flagmanagement'] = array( + 'type' => 'special' +); + +// Message flags +$_prefs['msgflags'] = array( + // Format: + // KEY: Flag name + // VALUE: Array with the following entries + // 'a' - (string) [abbreviation] The abbreviation used in + // the mimp (minimal) view. + // DEFAULT: Don't show flag + // 'b' - (string) [background] The CSS background color + // DEFAULT: Use value of 'msgflags_color' + // 'c' - (string) [class] The CSS background class (used to + // display status icon). + // NO DEFAULT (entry required) + // 'd' - (boolean) [delete] If true, entry can be deleted. + // DEFAULT: false + // 'l' - (string) [label] The flag text label. + // NO DEFAULT (entry required) + // 't' - (string) [type] The flag type: + // 'atc' - Attachment information + // 'imap' - IMAP flags (not user settable) + // 'imapp' - IMAP flags (personal flags - created by user + through the prefs interface) + // 'imapu' - IMAP flags (user settable) + // 'imp' - IMP defined flags + // NO DEFAULT (entry required) + 'value' => json_encode(array( + // Static internal imp flags (i.e. status icons) + // THESE ENTRIES MUST NOT BE DELETED + 'personal' => array( + 'a' => '+', + 'c' => 'flagPersonal', + 'l' => _("Personal"), + 't' => 'imp' + ), + 'highpri' => array( + 'a' => '^', + 'b' => '#ffcccc', + 'c' => 'flagHighpriority', + 'l' => _("High Priority"), + 't' => 'imp' + ), + 'lowpri' => array( + 'a' => 'v', + 'c' => 'flagLowpriority', + 'l' => _("Low Priority"), + 't' => 'imp' + ), + + // Attachment flags + // THESE ENTRIES MUST NOT BE DELETED + 'signed' => array( + 'c' => 'flagSignedmsg', + 'l' => _("Message is Signed"), + 't' => 'atc' + ), + 'encrypt' => array( + 'c' => 'flagEncryptmsg', + 'l' => _("Message is Encrypted"), + 't' => 'atc' + ), + 'attach' => array( + 'c' => 'flagAttachmsg', + 'l' => _("Message has Attachments"), + 't' => 'atc' + ), + + // IMAP flags + // KEY: IMAP flag as it exsits on the IMAP server + // VALUES (additional to base values): + // 'n' - (boolean) [NOT match] Don't match the flag. + // DEFAULT: false + + // System IMAP flags (RFC 3501 [2.3.2]) + '\\seen' => array( + 'a' => 'N', + 'b' => '#eeeeff', + 'c' => 'flagUnseen', + 'l' => _("Unseen"), + 'n' => true, + 't' => 'imap' + ), + '\\answered' => array( + 'a' => 'r', + 'b' => '#ccffcc', + 'c' => 'flagAnswered', + 'l' => _("Answered"), + 't' => 'imap' + ), + '\\draft' => array( + 'a' => 'd', + 'c' => 'flagDraft', + 'l' => _("Draft"), + 't' => 'imapu' + ), + '\\flagged' => array( + 'a' => '*', + 'b' => '#ffcccc', + 'c' => 'flagFlagged', + 'l' => _("Flagged for Followup"), + 't' => 'imapu' + ), + '\\deleted' => array( + 'a' => 'D', + 'b' => '#999999', + 'c' => 'flagDeleted', + 'l' => _("Deleted"), + 't' => 'imap' + ), + + // Forwarded flag (RFC 4550 [2.8]) + '$forwarded' => array( + 'a' => 'F', + 'b' => '#aadddd', + 'c' => 'flagForwarded', + 'l' => _("Forwarded"), + // Pursuant to RFC, this flag SHOULD NOT be changed by the user + 't' => 'imap' + ) + )), + 'locked' => false, + 'shared' => false, + 'type' => 'implicit' +); + +// The default color to use for flags that don't require row highlighting. +$_prefs['msgflags_color'] = array( + 'value' => '#ffffff', + 'locked' => false, + 'shared' => false, + 'type' => 'implicit'); + +// By default, don't allow user to alter flags set by system events. +$_prefs['msgflags_hidesys'] = array( + 'value' => true, + 'locked' => false, + 'shared' => false, + 'type' => 'implicit'); + +// End IMAP Flag preferences + + // Fetch Mail preferences // Change this if you want to customize how fetchmailprefs.php works. @@ -1087,6 +1243,7 @@ $_prefs['fetchmail_menu'] = array( // Don't change anything here. $_prefs['fm_accounts'] = array( + // value = serialize(array()) 'value' => 'a:0:{}', 'locked' => false, 'shared' => false, @@ -1143,6 +1300,7 @@ $_prefs['sortdir'] = array( // sort prefs for individual folders $_prefs['sortpref'] = array( + // value = serialize(array()) 'value' => 'a:0:{}', 'locked' => false, 'shared' => false, @@ -1169,6 +1327,16 @@ $_prefs['from_link'] = array( ), 'desc' => _("The From: column of the message should be linked:")); +// Display attachment information in mailbox list. +// Disabled by default, and not shown to user, because this display requires +// substantial overhead to parse the message structures of all message in +// the mailbox list at view time. +$_prefs['atc_flag'] = array( + 'value' => 0, + 'locked' => true, + 'type' => 'checkbox', + 'desc' => _("Display attachment information about a message in the mailbox listing?")); + // Time format for messages dated today $_prefs['time_format'] = array( 'value' => '%X', @@ -1227,6 +1395,7 @@ $_prefs['nav_poll_all'] = array( // list of folders to expand by default $_prefs['expanded_folders'] = array( + // value = serialize(array()) 'value' => 'a:0:{}', 'locked' => false, 'shared' => false, diff --git a/imp/docs/CHANGES b/imp/docs/CHANGES index 759a861d4..8c5373db9 100644 --- a/imp/docs/CHANGES +++ b/imp/docs/CHANGES @@ -2,6 +2,10 @@ v5.0-git -------- +[mms] Add support for defining and displaying custom IMAP flags and for + configuring the highlighting of system flags (Request #937). +[mms] Move attachment dispaly on mailbox page from hook to preference. +[mms] Always do X-Priority header processing by default. [mms] Support $MDNSent keyword (RFC 3503) on IMAP server. [mms] Link URLs/e-mails in subjects in message views (Request #7487). [mms] Implement spellcheck on send in DIMP. diff --git a/imp/docs/RFCS b/imp/docs/RFCS index 57775b823..be0b6dd6c 100644 --- a/imp/docs/RFCS +++ b/imp/docs/RFCS @@ -31,6 +31,7 @@ RFC 3691 UNSELECT RFC 4315 UIDPLUS RFC 4422 SASL Authentication (for DIGEST-MD5) RFC 4466 Collected extensions (updates RFCs 2088, 3501, 3502, 3516) +RFC 4550 Lemonade Profile (specifically [2.8] - $Forwarded flag) RFC 4551 CONDSTORE RFC 4731 ESEARCH RFC 4959 SASL-IR diff --git a/imp/docs/UPGRADING b/imp/docs/UPGRADING index 4a12ef1fb..7256c33c8 100644 --- a/imp/docs/UPGRADING +++ b/imp/docs/UPGRADING @@ -25,7 +25,11 @@ supported. * imp_hook_vinfo has changed * alternative_display, attachment_display, forward_default pref is gone. * imp_hook_spam_bounce -> imp_hook_spam_email -* imp_hook_msglist_format input has changed. +* imp_hook_msglist_format hook has been removed - instead: +* x-priority now handled in the core code +* old attachment icon handling see atc_flag pref +* imp_hook_msglist_flags hook now used to dynamically set flags on messages + Upgrading IMP From 4.1.x To 4.2.x diff --git a/imp/js/src/ContextSensitive.js b/imp/js/src/ContextSensitive.js index c990453b4..6b7dc417b 100644 --- a/imp/js/src/ContextSensitive.js +++ b/imp/js/src/ContextSensitive.js @@ -30,9 +30,15 @@ var ContextSensitive = Class.create({ + queueSettings: { + limit: 5, + position: 'end', + scope: 'contextsensitive' + }, + initialize: function(opts) { - this.basectx = this.target = null; + this.basectx = this.submenu = null; this.elements = $H(); this.submenus = $H(); this.current = []; @@ -66,9 +72,9 @@ var ContextSensitive = Class.create({ /** * Hide the currently displayed element(s). */ - close: function(immediate) + close: function() { - this._closeSubmenu(0, immediate); + this._closeSubmenu(0, true); }, /** @@ -78,14 +84,12 @@ var ContextSensitive = Class.create({ { if (this.current.size()) { this.current.splice(idx, this.current.size() - idx).each(function(s) { - if (immediate) { - $(s).hide(); - } else { - Effect.Fade(s, { duration: 0.2, queue: { position: 'end', scope: 'cm_' + s, limit: 2 } }); - } - }); - this.target = this.current[idx]; - this.basectx = null; + Effect.Fade(s, { + duration: 0.15, + afterFinish: function() { this.basectx = this.submenu = null; }.bind(this), + queue: immediate ? 'global' : this.queueSettings + }); + }, this); } }, @@ -124,7 +128,7 @@ var ContextSensitive = Class.create({ */ _leftClickHandler: function(e) { - var curr, elt, elt_up; + var base, elt, elt_up, submenu; // Check for a right click. FF on Linux triggers an onclick event even // w/a right click, so disregard. @@ -144,10 +148,11 @@ var ContextSensitive = Class.create({ elt_up.readAttribute('id') != this.currentmenu()) { this._closeSubmenu(this.current.indexOf(elt.readAttribute('id'))); } else { - curr = $(this.target); - this.close(true); + base = this.current.first(); + submenu = this.submenu; + this.close(); if (this.opts.onClick) { - this.opts.onClick(elt.readAttribute('id'), curr); + this.opts.onClick(elt, base, submenu); } } return; @@ -202,7 +207,6 @@ var ContextSensitive = Class.create({ // Register the current element that will be shown and the element // that was clicked on. this.close(); - this.target = ctx.id; offset = ctx.opts.offset; if (!offset && (Object.isUndefined(x) || Object.isUndefined(y))) { @@ -226,7 +230,7 @@ var ContextSensitive = Class.create({ /** * Display the [sub]menu on the screen. */ - _displayMenu: function(elt, x, y) + _displayMenu: function(elt, x, y, submenu) { // Get window/element dimensions var id = elt.readAttribute('id'), @@ -245,9 +249,16 @@ var ContextSensitive = Class.create({ this.opts.onShow(id, this.basectx); } - Effect.Appear(elt.setStyle({ left: x + 'px', top: y + 'px' }), { duration: 0.2, queue: { position: 'end', scope: 'cm_' + id, limit: 2 } }); - - this.current.push(id); + Effect.Appear(elt, { + from: 0.0, + afterSetup: function() { + elt.setStyle({ left: x + 'px', top: y + 'px' }) + this.current.push(id); + this.submenu = submenu; + }.bind(this), + duration: 0.15, + queue: this.queueSettings + }); }, /** @@ -288,7 +299,7 @@ var ContextSensitive = Class.create({ if (elt.hasClassName('contextSubmenu')) { sub = this.submenus.get(id); - if (sub != cm) { + if (sub != cm || this.submenu != id) { if (id_div != cm) { this._closeSubmenu(this.current.indexOf(id_div) + 1); } @@ -297,7 +308,7 @@ var ContextSensitive = Class.create({ voffsets = document.viewport.getScrollOffsets(); x = offsets[0] + voffsets.left + elt.getWidth(); y = offsets[1] + voffsets.top; - this._displayMenu($(sub), x, y); + this._displayMenu($(sub), x, y, id); } } else if ((this.current.size() > 1) && elt_up.hasClassName('contextMenu') && diff --git a/imp/js/src/DimpBase.js b/imp/js/src/DimpBase.js index 158057c22..41bc7eae6 100644 --- a/imp/js/src/DimpBase.js +++ b/imp/js/src/DimpBase.js @@ -31,17 +31,6 @@ var DimpBase = { sf_subject: 'subject' }), - flags: $H({ - unseen: 'Unseen', - flagged: 'Flagged', - deletedmsg: 'Deleted', - unimportant: 'LowPriority', - important: 'HighPriority', - answered: 'Answered', - forwarded: 'Forwarded', - draft: 'Draft' - }), - // Message selection functions // vs = (ViewPort_Selection) A ViewPort_Selection object. @@ -342,7 +331,7 @@ var DimpBase = { // r = ViewPort row data msgWindow: function(r) { - this.updateUnseenUID(r, 0); + this.updateSeenUID(r, 1); var url = DIMP.conf.message_url; url += (url.include('?') ? '&' : '?') + $H({ folder: r.view, @@ -418,10 +407,13 @@ var DimpBase = { search = this.sfilters.get(this._getSearchfilterField()).capitalize(); mf = new RegExp("(" + $F('msgList_filter') + ")", "i"); } + rows.get('dataob').each(function(row) { var elt, tmp, u, r = $(row.domid); + this.updateStatusFlags(row); + // Add thread graphics if (thread && thread.get(row.imapuid)) { elt = r.down('.msgSubject'); @@ -574,9 +566,6 @@ var DimpBase = { } return this.cacheids[id]; }.bind(this), - onUpdateClass: function(row) { - this.updateStatusFlags(row); - }.bind(this), onSplitBarChange: function() { this._updatePrefs('dimp_splitbar', this.viewport.getPageSize()); }.bind(this), @@ -619,27 +608,29 @@ var DimpBase = { DimpCore.DMenu.removeElement($(elt).identify()); }, - contextOnClick: function(parentfunc, id, elt) + contextOnClick: function(parentfunc, elt, base, submenu) { + var id = elt.readAttribute('id'); + switch (id) { case 'ctx_folder_create': - this.createSubFolder(elt); + this.createSubFolder(base); break; case 'ctx_container_rename': case 'ctx_folder_rename': - this.renameFolder(elt); + this.renameFolder(base); break; case 'ctx_folder_empty': - mbox = elt.readAttribute('mbox'); + mbox = base.readAttribute('mbox'); if (window.confirm(DIMP.text.empty_folder)) { DimpCore.doAction('EmptyFolder', { view: mbox }, null, this._emptyFolderCallback.bind(this)); } break; case 'ctx_folder_delete': - mbox = elt.readAttribute('mbox'); + mbox = base.readAttribute('mbox'); if (window.confirm(DIMP.text.delete_folder)) { DimpCore.doAction('DeleteFolder', { view: mbox }, null, this.bcache.get('folderC') || this.bcache.set('folderC', this._folderCallback.bind(this))); } @@ -647,25 +638,28 @@ var DimpBase = { case 'ctx_folder_seen': case 'ctx_folder_unseen': - this.flag(id == 'ctx_folder_seen' ? 'allSeen' : 'allUnseen', { mailbox: elt.readAttribute('mbox') }); + this.flag(id == 'ctx_folder_seen' ? 'allSeen' : 'allUnseen', { mailbox: base.readAttribute('mbox') }); break; case 'ctx_folder_poll': case 'ctx_folder_nopoll': - this.modifyPollFolder(elt.readAttribute('mbox'), id == 'ctx_folder_poll'); + this.modifyPollFolder(base.readAttribute('mbox'), id == 'ctx_folder_poll'); break; case 'ctx_container_create': - this.createSubFolder(elt); + this.createSubFolder(base); break; case 'ctx_message_spam': case 'ctx_message_ham': case 'ctx_message_blacklist': case 'ctx_message_whitelist': + this.flag(id.substring(12)); + break; + case 'ctx_message_deleted': case 'ctx_message_undeleted': - this.flag(id.substring(12)); + this.flag('imapflag', { imap: '\\deleted', set: id == 'ctx_message_deleted' }); break; case 'ctx_message_forward': @@ -676,11 +670,9 @@ var DimpBase = { this.composeMailbox('resume'); break; - case 'ctx_draft_flagged': - case 'ctx_draft_clear': case 'ctx_draft_deleted': case 'ctx_draft_undeleted': - this.flag(id.substring(10)); + this.flag('imapflag', { imap: '\\deleted', set: id == 'ctx_draft_deleted' }); break; case 'ctx_reply_reply': @@ -693,23 +685,14 @@ var DimpBase = { this.togglePreviewPane(); break; - case 'flag_seen': - case 'flag_unseen': - case 'flag_flagged': - case 'flag_clear': - case 'flag_answered': - case 'flag_unanswered': - case 'flag_draft': - case 'flag_notdraft': - this.flag(id.substring(5)); - break; - case 'oa_blacklist': case 'oa_whitelist': - case 'oa_undeleted': this.flag(id.substring(3)); break; + case 'oa_undeleted': + this.flag('imapflag', { imap: '\\deleted', set: false }); + case 'oa_selectall': this.selectAll(); break; @@ -719,7 +702,17 @@ var DimpBase = { break; default: - parentfunc(id, elt); + if (submenu == 'ctx_message_setflag' || + submenu == 'ctx_draft_setflag' || + submenu == 'oa_setflag') { + this.flag('imapflag', { imap: elt.readAttribute('flag'), set: true }); + } else if (submenu == 'ctx_message_unsetflag' || + submenu == 'ctx_draft_unsetflag' || + submenu == 'oa_unsetflag') { + this.flag('imapflag', { imap: elt.readAttribute('flag'), set: false }); + } else { + parentfunc(elt, base, submenu); + } break; } }, @@ -734,7 +727,8 @@ var DimpBase = { folder = $(ctx.ctx); if (folder.readAttribute('mbox') == 'INBOX') { elts.invoke('hide'); - } else if (DIMP.conf.fixed_folders.indexOf(folder.readAttribute('mbox')) != -1) { + } else if (DIMP.conf.fixed_folders && + DIMP.conf.fixed_folders.indexOf(folder.readAttribute('mbox')) != -1) { elts.shift(); elts.invoke('hide'); } else { @@ -899,8 +893,8 @@ var DimpBase = { // There is a chance that the message may have been marked // as unseen since first being viewed. If so, we need to // explicitly flag as seen here. - if (data.bg.indexOf('unseen') != -1) { - this.flag('seen'); + if (this.isUnseen(data)) { + this.flag('imapflag', { imap: '\\seen', set: true }); } return this._loadPreviewCallback(this.ppcache[pp_uid]); } @@ -923,7 +917,7 @@ var DimpBase = { search = this.viewport.getViewportSelection().search({ imapuid: { equal: [ r.index ] }, view: { equal: [ r.folder ] } }); if (search.size()) { row = search.get('dataob').first(); - this.updateUnseenUID(row, 0); + this.updateSeenUID(row, 1); } } @@ -958,7 +952,7 @@ var DimpBase = { switch (r.priority) { case 'high': case 'low': - tmp.invoke('insert', { top: new Element('SPAN').addClassName('status' + r.priority.capitalize() + 'Priority') }); + tmp.invoke('insert', { top: new Element('DIV').addClassName('flag' + r.priority.capitalize() + 'priority') }); break; } @@ -1047,25 +1041,24 @@ var DimpBase = { }, // Labeling functions - updateUnseenUID: function(r, setflag) + updateSeenUID: function(r, setflag) { - var sel, unseen, unseenset; - if (!r.bg) { - return false; - } - unseenset = r.bg.indexOf('unseen') != -1; - if ((setflag && unseenset) || (!setflag && !unseenset)) { + var isunseen = this.isUnseen(r), + sel, unseen; + + if ((setflag && !isunseen) || (!setflag && isunseen)) { return false; } sel = this.viewport.createSelection('dataob', r); unseen = Number($(this.getFolderId(r.view)).readAttribute('u')); + if (setflag) { - this.viewport.updateFlag(sel, 'unseen', true); - ++unseen; - } else { - this.viewport.updateFlag(sel, 'unseen', false); + this.updateFlag(sel, '\\seen', true); --unseen; + } else { + this.updateFlag(sel, '\\seen', false); + ++unseen; } this.updateUnseenStatus(r.view, unseen); @@ -1333,7 +1326,7 @@ var DimpBase = { DimpCore.doAction('CopyMessage', this.viewport.addRequestParams({ tofld: foldername }), uids, this.bcache.get('pollFC') || this.bcache.set('pollFC', this._pollFoldersCallback.bind(this))); } else if (this.folder != foldername) { // Don't allow drag/drop to the current folder. - this.viewport.updateFlag(uids, 'deletedmsg', true); + this.updateFlag(uids, '\\deleted', true); DimpCore.doAction('MoveMessage', this.viewport.addRequestParams({ tofld: foldername }), uids, this.bcache.get('deleteC') || this.bcache.set('deleteC', this._deleteCallback.bind(this))); } } @@ -1396,7 +1389,7 @@ var DimpBase = { if (e.shiftKey) { this.moveSelected((r.last().rownum == this.viewport.getMetaData('total_rows')) ? (r.first().rownum - 1) : (r.last().rownum + 1), true); } - this.flag('deleted', { index: r }); + this.flag('imapflag', { imap: '\\deleted', index: r, set: true }); e.stop(); break; @@ -1516,7 +1509,7 @@ var DimpBase = { } var elt = e.element(), - id, mbox, tmp; + id, tmp; while (Object.isElement(elt)) { id = elt.readAttribute('id'); @@ -1581,13 +1574,17 @@ var DimpBase = { this.composeMailbox(id == 'button_reply' ? 'reply' : 'forward'); break; - case 'button_deleted': case 'button_ham': case 'button_spam': this.flag(id.substring(7)); e.stop(); return; + case 'button_deleted': + this.flag('imapflag', { imap: '\\deleted', set: true }); + e.stop(); + return; + case 'button_other': DimpCore.DMenu.trigger(e.findElement('A').next(), true); e.stop(); @@ -1816,7 +1813,7 @@ var DimpBase = { this._expirePPCache(uids); } else { // Need this to catch spam deletions. - this.viewport.updateFlag(search, 'deletedmsg', true); + this.updateFlag(search, '\\deleted', true); } } }, @@ -2059,12 +2056,11 @@ var DimpBase = { }, /* Flag actions for message list. */ - // opts = 'index', 'mailbox', 'noserver' (only for answered/unanswered) + // opts = 'imap' 'index', 'mailbox', 'noserver' (only for answered/unanswered), 'set' flag: function(action, opts) { var actionCall, args, vs, - obs = [], - unseenstatus = 1; + flags = []; opts = opts || {}; if (opts.index) { @@ -2085,12 +2081,10 @@ var DimpBase = { case 'allSeen': DimpCore.doAction((action == 'allUnseen') ? 'MarkFolderUnseen' : 'MarkFolderSeen', { view: opts.mailbox }, null, this.bcache.get('flagAC') || this.bcache.set('flagAC', this._flagAllCallback.bind(this))); if (opts.mailbox == this.folder) { - this.viewport.updateFlag(this.createSelection('rownum', $A($R(1, this.viewport.getMetaData('total_rows')))), 'unseen', action == 'allUnseen'); + this.updateFlag(this.createSelection('rownum', $A($R(1, this.viewport.getMetaData('total_rows')))), '\\seen', action != 'allUnseen'); } break; - case 'deleted': - case 'undeleted': case 'spam': case 'ham': case 'blacklist': @@ -2099,146 +2093,136 @@ var DimpBase = { break; } - // Make sure that any given row is not deleted more than once. - // Need to explicitly mark here because message may already be - // flagged deleted when we load page (i.e. switching to using - // trash folder). - if (action == 'deleted') { - vs = vs.search({ isdel: { not: [ true ] } }); - if (!vs.size()) { - break; - } - vs.set({ isdel: true }); - } - args = this.viewport.addRequestParams({}); - if (action == 'deleted' || action == 'undeleted') { - this.viewport.updateFlag(vs, 'deletedmsg', action == 'deleted'); } - if (action == 'undeleted') { - DimpCore.doAction('UndeleteMessage', args, vs); - vs.set({ isdel: false }); - } else { - actionCall = { deleted: 'DeleteMessage', spam: 'ReportSpam', ham: 'ReportHam', blacklist: 'Blacklist', whitelist: 'Whitelist' }; - // This needs to be synchronous Ajax if we are calling from a - // popup window because Mozilla will not correctly call the - // callback function if the calling window has been closed. - DimpCore.doAction(actionCall[action], args, vs, this.bcache.get('deleteC') || this.bcache.set('deleteC', this._deleteCallback.bind(this)), { asynchronous: !(opts.index && opts.mailbox) }); - - // If reporting spam, to indicate to the user that something is - // happening (since spam reporting may not be instantaneous). - if (action == 'spam' || action == 'ham') { - this.msgListLoading(true); - } - } - break; + actionCall = { + spam: 'ReportSpam', + ham: 'ReportHam', + blacklist: 'Blacklist', + whitelist: 'Whitelist' + }; - case 'unseen': - case 'seen': - if (!vs.size()) { - break; - } - args = { view: this.folder, flags: [ '-\\seen' ].toJSON() }; - if (action == 'seen') { - unseenstatus = 0; - args.flags = [ '\\seen' ].toJSON(); - } - obs = vs.get('dataob'); - if (obs.size()) { - obs.each(function(s) { - this.updateUnseenUID(s, unseenstatus); - }, this); - DimpCore.doAction('MarkMessage', args, this.viewport.createSelection('dataob', obs)); + // This needs to be synchronous Ajax if we are calling from a + // popup window because Mozilla will not correctly call the + // callback function if the calling window has been closed. + DimpCore.doAction(actionCall[action], this.viewport.addRequestParams({}), vs, this.bcache.get('deleteC') || this.bcache.set('deleteC', this._deleteCallback.bind(this)), { asynchronous: !(opts.index && opts.mailbox) }); + + // If reporting spam, to indicate to the user that something is + // happening (since spam reporting may not be instantaneous). + if (action == 'spam' || action == 'ham') { + this.msgListLoading(true); } break; - case 'flagged': - case 'clear': + case 'imapflag': if (!vs.size()) { break; } - args = { - view: this.folder, - flags: [ ((action == 'flagged') ? '' : '-') + '\\flagged' ].toJSON() - }; - this.viewport.updateFlag(vs, 'flagged', action == 'flagged'); - DimpCore.doAction('MarkMessage', args, vs); - break; - case 'answered': - if (!vs.size()) { + flags = [ (opts.set ? '' : '-') + opts.imap ]; + + switch (opts.imap) { + case '\\deleted': + // Make sure that any given row is not deleted more than once. + // Need to explicitly mark here because message may already be + // flagged deleted when we load page (i.e. switching to using + // trash folder). + if (opts.set) { + vs = vs.search({ isdel: { not: [ true ] } }); + if (!vs.size()) { + return; + } + vs.set({ isdel: true }); + } else { + vs.set({ isdel: false }); + } + + this.updateFlag(vs, opts.imap, opts.set); + DimpCore.doAction(opts.set ? 'DeleteMessage' : 'UndeleteMessage', this.viewport.addRequestParams({}), vs, this.bcache.get('deleteC') || this.bcache.set('deleteC', this._deleteCallback.bind(this)), { asynchronous: !(opts.index && opts.mailbox) }); + return; + + case '\\seen': + vs.get('dataob').each(function(s) { + this.updateSeenUID(s, opts.set); + }, this); break; - } - this.viewport.updateFlag(vs, 'answered', true); - this.viewport.updateFlag(vs, 'flagged', false); - if (!opts.noserver) { - args = { - view: this.folder, - flags: [ '\\answered', '-\\flagged' ].toJSON() - }; - DimpCore.doAction('MarkMessage', args, vs); - } - break; - case 'unanswered': - if (!vs.size()) { + case '\\answered': + if (opts.set) { + this.updateFlag(vs, '\\flagged', false); + flags.push('-\\flagged'); + } break; } - this.viewport.updateFlag(vs, 'answered', false); + + this.updateFlag(vs, opts.imap, opts.set); if (!opts.noserver) { - args = { - view: this.folder, - flags: [ '-\\answered' ].toJSON() - }; - DimpCore.doAction('MarkMessage', args, vs); + DimpCore.doAction('MarkMessage', { flags: flags.toJSON(), view: this.folder }, vs); } - break; + } + }, - case 'draft': - case 'notdraft': - if (!vs.size()) { - break; - } - args = { - view: this.folder, - flags: [ ((action == 'draft') ? '' : '-') + '\\draft' ].toJSON() - }; - this.viewport.updateFlag(vs, 'draft', action == 'draft'); - DimpCore.doAction('MarkMessage', args, vs); - break; + isUnseen: function(r) + { + /* Unseen is a weird flag. Since we are doing a reverse match on this + * flag (knowing a message is SEEN is not as important as knowing the + * message lacks the SEEN FLAG), the presence of \\seen indicates that + * the message is in reality unseen. */ + return r.flag.include('\\seen'); + }, - case 'forwarded': - this.viewport.updateFlag(vs, 'forwarded', true); - break; + updateFlag: function(vs, flag, add) + { + /* See isUnseen() - if flag is \\seen, need to do the opposite + * action. */ + if (flag == '\\seen') { + add = !add; } + + vs.get('dataob').each(function(ob) { + ob.flag = ob.flag.without(flag); + if (add) { + ob.flag.push(flag); + } else { + var r = $(ob.domid); + if (r) { + r.removeClassName(DIMP.conf.flags[flag].c); + } + } + this.updateStatusFlags(ob); + }, this); }, updateStatusFlags: function(row) { - var elt = new Element('DIV'), + var bg = null, + f = document.createDocumentFragment(), r = $(row.domid), - s = r.down('.msgStatus'), - tmp; + s; - // Add attachment graphic - if (row.atc) { - tmp = 'status' + row.atc.capitalize(); - if (!s.down('.' + tmp)) { - s.insert($(elt.cloneNode(false)).writeAttribute({ className: tmp, title: DIMP.conf.atc_list[row.atc] || null })); - } + if (!r) { + return; } - this.flags.each(function(c) { - tmp = 'status' + c.value; - var d = s.down('.' + tmp); - if (r.hasClassName(c.key)) { - if (!d) { - s.insert($(elt.cloneNode(false)).writeAttribute({ className: tmp, title: DIMP.text[tmp] || null })); - } - } else if (d) { - d.remove(); + s = r.down('.msgStatus'); + + row.flag.each(function(a) { + var ptr = DIMP.conf.flags[a]; + if (!ptr.elt) { + ptr.elt = new Element('DIV', { className: 'msgflags ' + ptr.c, title: ptr.l }); + } + r.addClassName(ptr.c); + f.appendChild(ptr.elt.cloneNode(false)); + if (ptr.b) { + bg = ptr.b; } }); + + /* Clear existing flags. */ + s.down().nextSiblings().invoke('remove'); + + s.appendChild(f); + r.setStyle({ background: bg }); }, /* Miscellaneous folder actions. */ @@ -2349,9 +2333,12 @@ var DimpBase = { /* Add popdown menus. Check for disabled compose at the same time. */ this._addMouseEvents({ id: 'button_other', type: 'otheractions' }, true); DM.addSubMenu('ctx_message_reply', 'ctx_reply'); - DM.addSubMenu('ctx_message_setflag', 'ctx_flag'); - DM.addSubMenu('oa_setflag', 'ctx_flag'); - DM.addSubMenu('ctx_draft_setflag', 'ctx_flag'); + [ 'ctx_message_', 'oa_', 'ctx_draft_' ].each(function(i) { + if ($(i + 'setflag')) { + DM.addSubMenu(i + 'setflag', 'ctx_flag'); + DM.addSubMenu(i + 'unsetflag', 'ctx_flag'); + } + }); if (DIMP.conf.disable_compose) { $('button_reply', 'button_forward').compact().invoke('up', 'SPAN').concat($('button_compose', 'composelink', 'ctx_contacts_new')).compact().invoke('remove'); diff --git a/imp/js/src/DimpCore.js b/imp/js/src/DimpCore.js index 15d7e8d2d..cbe26ca4f 100644 --- a/imp/js/src/DimpCore.js +++ b/imp/js/src/DimpCore.js @@ -524,9 +524,9 @@ DimpCore = { // By default, no context onShow action contextOnShow: Prototype.emptyFunction, - contextOnClick: function(id, elt) + contextOnClick: function(elt, base, submenu) { - switch (id) { + switch (elt.readAttribute('id')) { case 'ctx_contacts_new': this.compose('new', { to: elt.readAttribute('address') }); break; diff --git a/imp/js/src/ViewPort.js b/imp/js/src/ViewPort.js index 47bf3942c..e955c7893 100644 --- a/imp/js/src/ViewPort.js +++ b/imp/js/src/ViewPort.js @@ -704,12 +704,6 @@ var ViewPort = Class.create({ c_nodes.push(this.template.evaluate(r)); }, this); c.update(c_nodes.join('')); - - if (this.opts.onUpdateClass) { - rows.get('dataob').each(function(d) { - this.opts.onUpdateClass(d); - }, this); - } } else { // If loading a viewport for the first time, show a blank // viewport rather than the empty viewport status message. @@ -790,54 +784,11 @@ var ViewPort = Class.create({ }, // vs = (Viewport_Selection) A Viewport_Selection object. - // flag = (string) Flag name. - // add = (boolean) Whether to set/unset flag. - updateFlag: function(vs, flag, add) - { - this._updateFlag(vs, flag, add, this.isFiltering()); - this._updateClass(vs, flag, add); - }, - - // vs = (Viewport_Selection) A Viewport_Selection object. - // flag = (string) Flag name. - // add = (boolean) Whether to set/unset flag. - // filter = (boolean) Are we filtering results? - _updateFlag: function(vs, flag, add, filter) - { - vs.get('dataob').each(function(r) { - if (add) { - r.bg.push(flag); - } else { - r.bg = r.bg.without(flag); - } - if (filter) { - this._updateFlag(this.createSelection('uid', r.vp_id, r.view), flag, add); - } - }, this); - }, - - // vs = (Viewport_Selection) A Viewport_Selection object. - // flag = (string) Flag name. + // cname = (string) Class name. // add = (boolean) Whether to set/unset flag. - _updateClass: function(vs, flag, add) + _updateClass: function(vs, cname, add) { - var divs = vs.get('div'), - sel = new ViewPort_Selection(this._getBuffer(), 'div', divs); - - divs.each(function(d) { - if (add) { - d.addClassName(flag); - } else { - d.removeClassName(flag); - } - }, this); - - - if (this.opts.onUpdateClass) { - sel.get('dataob').each(function(d) { - this.opts.onUpdateClass(d); - }, this); - } + vs.get('div').invoke(add ? 'addClassName' : 'removeClassName', cname); }, _getLineHeight: function() diff --git a/imp/js/src/compose-dimp.js b/imp/js/src/compose-dimp.js index d33c5e590..45d200640 100644 --- a/imp/js/src/compose-dimp.js +++ b/imp/js/src/compose-dimp.js @@ -236,16 +236,7 @@ var DimpCompose = { case 'send_message': this.button_pressed = false; if (DIMP.baseWindow) { - switch (d.reply_type) { - case 'reply': - DIMP.baseWindow.DimpBase.flag('answered', { index: d.index, mailbox: d.reply_folder, noserver: true }); - break; - - case 'forward': - DIMP.baseWindow.DimpBase.flag('forwarded', { index: d.index, mailbox: d.reply_folder, noserver: true }); - break; - } - + DIMP.baseWindow.DimpBase.flag('imapflag', { imap: d.reply_type == 'reply' ? '\\answered' : '$forwarded', index: d.index, mailbox: d.reply_folder, noserver: true, set: true }); if (d.folder) { DIMP.baseWindow.DimpBase.createFolder(d.folder); diff --git a/imp/js/src/flagmanagement.js b/imp/js/src/flagmanagement.js new file mode 100644 index 000000000..92ef8e720 --- /dev/null +++ b/imp/js/src/flagmanagement.js @@ -0,0 +1,78 @@ +/** + * Provides the javascript for managing message flags. + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + */ + +var ImpFlagmanagement = { + // Variables set by other code: new_prompt + + addFlag: function() + { + var category = window.prompt(this.new_prompt, ''); + if (category) { + this._sendData('add', category); + } + }, + + _sendData: function(a, d) + { + $('flag_action').setValue(a) + $('flag_data').setValue(d); + $('prefs').submit(); + }, + + clickHandler: function(e) + { + if (e.isRightClick()) { + return; + } + + var elt = e.element(), elt2, id; + + while (Object.isElement(elt)) { + if (elt.hasClassName('flagcolorpicker')) { + elt2 = elt.previous('INPUT'); + id = elt2.readAttribute('id'); + new ColorPicker({ + color: $F(elt2), + offsetParent: elt, + update: [[ id, 'value' ], [ id, 'background' ]] + }); + e.stop(); + return; + } + + if (elt.hasClassName('flagdelete')) { + this._sendData('delete', elt.previous('INPUT').readAttribute('id')); + e.stop(); + return; + } + + switch (elt.readAttribute('id')) { + case 'new_button': + this.addFlag(); + break; + } + + elt = elt.up(); + } + }, + + resetHandler: function() + { + $('prefs').getInputs('text').each(function(i) { + if (i.readAttribute('id').startsWith('color_')) { + i.setStyle({ backgroundColor: $F(i) }); + } + }); + } + +}; + +document.observe('dom:loaded', function() { + var fm = ImpFlagmanagement; + document.observe('click', fm.clickHandler.bindAsEventListener(fm)); + $('prefs').observe('reset', function() { fm.resetHandler.defer(); }); +}); diff --git a/imp/js/src/fullmessage-dimp.js b/imp/js/src/fullmessage-dimp.js index 0692ad703..60076ecc3 100644 --- a/imp/js/src/fullmessage-dimp.js +++ b/imp/js/src/fullmessage-dimp.js @@ -91,7 +91,11 @@ var DimpFullmessage = { case 'button_deleted': case 'button_ham': case 'button_spam': - DIMP.baseWindow.DimpBase.flag(id.substring(7), { index: DIMP.conf.msg_index, mailbox: DIMP.conf.msg_folder }); + if (id == 'button_deleted') { + DIMP.baseWindow.DimpBase.flag('imapflag', { imap: '\\deleted', index: DIMP.conf.msg_index, mailbox: DIMP.conf.msg_folder, set: true }); + } else { + DIMP.baseWindow.DimpBase.flag(id.substring(7), { index: DIMP.conf.msg_index, mailbox: DIMP.conf.msg_folder }); + } window.close(); e.stop(); return; @@ -109,8 +113,10 @@ var DimpFullmessage = { parentfunc(e); }, - contextOnClick: function(parentfunc, id, elt) + contextOnClick: function(parentfunc, elt, base, submenu) { + var id = elt.readAttribute('id'); + switch (id) { case 'ctx_reply_reply': case 'ctx_reply_reply_all': @@ -119,7 +125,7 @@ var DimpFullmessage = { break; default: - parentfunc(id, elt); + parentfunc(elt, base, submenu); break; } }, diff --git a/imp/js/src/mailbox.js b/imp/js/src/mailbox.js index 5d00ffd24..5f77f3834 100644 --- a/imp/js/src/mailbox.js +++ b/imp/js/src/mailbox.js @@ -5,31 +5,25 @@ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. */ -var ImpMessage = { +var ImpMailbox = { // The following variables are defined in mailbox.php: - // messagelist, sortlimit, unread - keyId: null, - startrange: null, + // sortlimit, unread anySelected: function() { - return $H(this.messagelist).keys().detect(function(e) { - return $('check' + e).checked; - }); + return $('messages').select('[name="indices[]"]').detect(Form.Element.getValue); }, selectRow: function(id, select) { - var rid = $(id.replace(/check/, 'row')); - if (select) { - rid.addClassName('selectedRow'); + id.addClassName('selectedRow'); } else { // Make sure to remove both regular and -over versions. - rid.removeClassName('selectedRow').removeClassName('selectedRow-over'); + id.removeClassName('selectedRow').removeClassName('selectedRow-over'); } - $(id).checked = select; + id.down('INPUT.checkbox').setValue(select); }, confirmDialog: function(url, msg) @@ -70,80 +64,39 @@ var ImpMessage = { $('messages').submit(); }, - makeSelection: function(form) - { - var flag = ''; - - switch (parseInt(form)) { - case -1: - if ($('checkAll').checked) { - flag = '!'; - } - flag += IMP.conf.IMP_ALL; - break; - - case 1: - flag = $F('filter1'); - break; - - default: - flag = $F('filter2'); - } - - // Fixes Bug #6893 - if (flag.empty()) { - return; - } else if (flag.startsWith('!')) { - this.selectFlagged(parseInt(flag.substring(1)), false); - } else if (flag.startsWith('+')) { - this.selectFlagged(flag.substring(0, 1), null); - } else { - this.selectFlagged(parseInt(flag), true); - } - - // Reset the form. - switch (parseInt(form)) { - case 1: - $('select1').reset(); - break; - - default: - $('select2').reset(); - } - }, - selectRange: function(e) { - var id = e.element().readAttribute('id'), - checkbox = $(id), - count = 0, - checked, elts; - - if (!checkbox) { - return; - } - - checked = checkbox.checked; - - if (this.startrange !== null && e.shiftKey) { - elts = [ $(this.startrange).readAttribute('id'), checkbox.readAttribute('id') ]; - $H(this.messagelist).keys().detect(function(r) { - r = 'check' + r; - if (elts.indexOf(r) != -1) { - ++count; + // elt = checkbox element + var elt = e.element(), + tr = elt.up('TR'), + checked = $F(elt), + end, start; + + if (this.startrange && e.shiftKey) { + if (this.startrange != elt) { + // Dirty trick - use position in page to determine which way + // to traverse + if (this.startrange.offsetTop < tr.offsetTop) { + start = this.startrange.next(); + end = tr; + } else { + start = tr; + end = this.startrange.previous(); } - if (count) { - this.selectRow(r, checked); - if (count == 2) { - return true; + + do { + this.selectRow(start, checked); + if (start == end) { + break; } - } - }, this); + start = start.next(); + } while (start); + } } else { - this.selectRow(id, checked); + this.selectRow(tr, checked); } - this.startrange = id; + this.startrange = tr; }, updateFolders: function(form) @@ -189,22 +142,6 @@ var ImpMessage = { } }, - // Put everything reliant on IMAP flags in this section. - selectFlagged: function(flag, val) - { - $H(this.messagelist).keys().each(function(e) { - var check, elt = $('check' + e); - if (flag == '+') { - check = !elt.checked; - } else if (flag & this.messagelist[e]) { - check = val; - } else { - check = !val; - } - this.selectRow(elt.id, check); - }, this); - }, - flagMessages: function(form) { var f1 = $('flag1'), f2 = $('flag2'); @@ -212,8 +149,7 @@ var ImpMessage = { if ((form == 1 && $F(f1) != "") || (form == 2 && $F(f2) != "")) { if (this.anySelected()) { - // Can't use $() here. See Bug #4736. - document.messages.flag.value = (form == 1) ? $F(f1) : $F(f2); + $('messages').down('[name=flag]').setValue((form == 1) ? $F(f1) : $F(f2)); this.submit('flag_messages'); } else { if (form == 1) { @@ -249,12 +185,12 @@ var ImpMessage = { { var id = e.element().readAttribute('id'); - if (id.startsWith('filter')) { - this.makeSelection(id.substring(6)); - } else if (id.startsWith('flag')) { - this.flagMessages(id.substring(4)); - } else if (id.startsWith('targetMailbox')) { - this.updateFolders(id.substring(13)); + if (id) { + if (id.startsWith('flag')) { + this.flagMessages(id.substring(4)); + } else if (id.startsWith('targetMailbox')) { + this.updateFolders(id.substring(13)); + } } }, @@ -267,8 +203,6 @@ var ImpMessage = { var elt = e.element(), id; while (Object.isElement(elt)) { - id = elt.readAttribute('id'); - if (elt.match('.msgactions A.widget')) { if (elt.hasClassName('moveAction')) { this._transfer('move_messages'); @@ -300,7 +234,13 @@ var ImpMessage = { e.stop(); return; - } else if (!id) { + } else if (elt.hasClassName('checkbox')) { + this.selectRange(e); + // Fall through to elt.up() call below. + } + + id = elt.readAttribute('id'); + if (!id) { elt = elt.up(); continue; } @@ -311,15 +251,16 @@ var ImpMessage = { if (id == 'checkheader') { $('checkAll').checked = !$('checkAll').checked; } - this.makeSelection(-1); + + $('messages').select('TABLE.messageList TR[id]').each(function(i, s) { + this.selectRow(i, $F('checkAll')); + }, this); return; } - if (id.startsWith('check') && elt.hasClassName('checkbox')) { - this.selectRange(e); - } else if (!this.sortlimit && - elt.match('TH') && - elt.up('TABLE.messageList')) { + if (!this.sortlimit && + elt.match('TH') && + elt.up('TABLE.messageList')) { document.location.href = elt.down('A').href; } @@ -329,82 +270,67 @@ var ImpMessage = { keyDownHandler: function(e) { - var o = e.element(), + var elt = e.element(), key = e.keyCode, - checkinc, loc, next, nextId, old, row, subjinc; + loc, search; if (e.altKey || e.ctrlKey) { - switch (key) { - case Event.KEY_UP: - checkinc = -1; - subjinc = -1; - break; - - case Event.KEY_DOWN: - checkinc = 1; - subjinc = 1; - break; - - default: + if (!(key == Event.KEY_UP || key == Event.KEY_DOWN)) { return; } - if (typeof this.messagelist == 'undefined') { - return; + if (!this.cursor) { + this.cursor = elt.up('TABLE.messageList TR'); } - if (o.id.indexOf('check') == 0 && o.tagName == 'INPUT') { - old = o.id.substring(5); - this.keyId = this.getMessage(old, checkinc); - next = $('subject' + this.keyId); - } else if (o.id.indexOf('subject') == 0 && o.tagName == 'A') { - old = o.id.substring(7); - this.keyId = this.getMessage(old, subjinc); - next = $('subject' + this.keyId); - } else { - this.keyId = ((checkinc + subjinc) > 0) ? $H(this.messagelist).keys().first() : $H(this.messagelist).keys().last(); - if (Event.KEY_UP || Event.KEY_DOWN) { - next = $('subject' + this.keyId); + if (this.cursor) { + if (e.altKey) { + this.selectRow(this.cursor, !$F(this.cursor.down('INPUT.checkbox'))); } + + switch (key) { + case Event.KEY_UP: + this.cursor = this.cursor.previous(); + if (!this.cursor.readAttribute('id')) { + search = 'last'; + } + break; + + case Event.KEY_DOWN: + this.cursor = this.cursor.next(); + if (!this.cursor) { + search = 'first'; + } + break; + } + } else { + search = Event.KEY_DOWN ? 'first' : 'last'; + } + + if (search) { + this.cursor = (search == 'first') + ? $('messages').select('TABLE.messageList TR[id]').first() + : $('messages').select('TABLE.messageList TR[id]').last(); } - } else if (key == 32 && - o.id.indexOf('subject') == 0 && - o.tagName == 'A') { - // Space key - toggle selection of the current message. - this.startrange = 'check' + this.keyId; - this.selectRow(this.startrange, !$(this.startrange).checked); + + this.cursor.down('TD a.mboxSubject').focus(); + } else if (key == 32 && this.cursor) { + this.selectRow(this.cursor, !$F(this.cursor.down('INPUT.checkbox'))); } else if (!e.shiftKey) { if (key == Event.KEY_LEFT && $('prev')) { - loc = $('prev').href; + loc = $('prev'); } else if (key == Event.KEY_RIGHT && $('next')) { - loc = $('next').href; + loc = $('next'); } if (loc) { - document.location.href = loc; + document.location.href = loc.readAttribute('href'); } return; } else { return; } - if (next) { - next.focus(); - row = $('row' + this.keyId); - if (e.altKey) { - nextId = next.id.replace(/subject/, 'check'); - this.selectRow(nextId, !$(nextId).checked); - } else if (old != next.id && row.className.indexOf('-over') == -1) { - row.className += '-over'; - } - if (old) { - row = $('row' + old); - if (old != next.id) { - row.className = row.className.replace(/-over/, ''); - } - } - } - e.stop(); }, @@ -417,15 +343,17 @@ var ImpMessage = { }; -document.observe('change', ImpMessage.changeHandler.bindAsEventListener(ImpMessage)); -document.observe('click', ImpMessage.clickHandler.bindAsEventListener(ImpMessage)); -document.observe('keydown', ImpMessage.keyDownHandler.bindAsEventListener(ImpMessage)); -document.observe('submit', ImpMessage.submitHandler.bindAsEventListener(ImpMessage)); +document.observe('dom:loaded', function() { + var im = ImpMailbox; + + document.observe('change', im.changeHandler.bindAsEventListener(im)); + document.observe('click', im.clickHandler.bindAsEventListener(im)); + document.observe('keydown', im.keyDownHandler.bindAsEventListener(im)); + document.observe('submit', im.submitHandler.bindAsEventListener(im)); -Event.observe(window, 'load', function() { if (window.fluid) { try { - window.fluid.setDockBadge(ImpMessage.unread); + window.fluid.setDockBadge(ImpMailbox.unread); } catch (e) {} } }); diff --git a/imp/js/src/message.js b/imp/js/src/message.js index e0304c860..a38c28262 100644 --- a/imp/js/src/message.js +++ b/imp/js/src/message.js @@ -53,9 +53,10 @@ var ImpMessage = { flagMessage: function(form) { var f1 = $('flag1'), f2 = $('flag2'); - if ((form == 1 && $F(f1)) || - (form == 2 && $F(f2))) { - $('flag').setValue((form == 1) ? $F(f1) : $F(f2)); + + if ((form == 1 && $F(f1) != "") || + (form == 2 && $F(f2) != "")) { + $('messages').down('[name=flag]').setValue((form == 1) ? $F(f1) : $F(f2)); this.submit('flag_message'); } }, diff --git a/imp/lib/DIMP.php b/imp/lib/DIMP.php index 5b901b5a7..58ce01789 100644 --- a/imp/lib/DIMP.php +++ b/imp/lib/DIMP.php @@ -148,6 +148,17 @@ class DIMP } } + /* Generate flag array. */ + $flags = array(); + $imp_flags = &IMP_Imap_Flags::singleton(); + foreach ($imp_flags->getList() as $val) { + $flags[$val['flag']] = array_filter(array( + 'b' => isset($val['b']) ? $val['b'] : null, + 'c' => $val['c'], + 'l' => $val['l'] + )); + } + /* Variables used in core javascript files. */ $code['conf'] = array_filter(array( 'URI_DIMP_INBOX' => Horde::applicationUrl('index-dimp.php', true, -1), @@ -169,6 +180,8 @@ class DIMP 'popup_width' => 820, 'popup_height' => 610, + 'flags' => $flags, + 'spam_folder' => IMP::folderPref($prefs->getValue('spam_folder'), true), 'spam_reporting' => intval(!empty($conf['spam']['reporting'])), 'spam_spamfolder' => intval(!empty($conf['spam']['spamfolder'])), @@ -197,8 +210,6 @@ class DIMP 'background_inbox' => intval(!empty($conf['dimp']['viewport']['background_inbox'])), 'splitbar_pos' => $prefs->getValue('dimp_splitbar'), - 'atc_list' => IMP_UI_Mailbox::getAttachmentAltList(), - // Turn debugging on? 'debug' => intval(!empty($conf['dimp']['js']['debug'])), )); @@ -239,10 +250,6 @@ class DIMP 'alog_message' => _("Message"), 'alog_success' => _("Success"), 'alog_warning' => _("Warning"), - 'statusAnswered' => _("Replied"), - 'statusForwarded' => _("Forwarded"), - 'statusUnseen' => _("Unseen"), - 'statusFlagged' => _("Flagged"), )); /* Gettext strings with individual escaping. */ diff --git a/imp/lib/IMP.php b/imp/lib/IMP.php index 6d90edefc..c71c405ca 100644 --- a/imp/lib/IMP.php +++ b/imp/lib/IMP.php @@ -25,16 +25,6 @@ class IMP const PGP_SYM_ENCRYPT = 8; const PGP_SYM_SIGNENC = 9; - /* IMAP flag constants. */ - const FLAG_ALL = 0; - const FLAG_UNSEEN = 1; - const FLAG_DELETED = 2; - const FLAG_ANSWERED = 4; - const FLAG_FLAGGED = 8; - const FLAG_DRAFT = 16; - const FLAG_PERSONAL = 32; - const FLAG_FORWARDED = 64; - /* IMP Mailbox view constants. */ const MAILBOX_START_FIRSTUNSEEN = 1; const MAILBOX_START_LASTUNSEEN = 2; @@ -244,9 +234,9 @@ class IMP (!empty($GLOBALS['conf']['hooks']['permsdenied']) || (self::hasPermission('create_folders') && self::hasPermission('max_folders')))) { - $text .= '' . "\n"; + $text .= '' . "\n"; $text .= '\n"; - $text .= '' . "\n"; + $text .= '' . "\n"; } /* Add the list of mailboxes to the lists. */ diff --git a/imp/lib/Imap/Flags.php b/imp/lib/Imap/Flags.php new file mode 100644 index 000000000..bca35ebba --- /dev/null +++ b/imp/lib/Imap/Flags.php @@ -0,0 +1,360 @@ + + * @package IMP + */ +class IMP_Imap_Flags +{ + /* IMAP flag prefix for IMP-specific flags. */ + const PREFIX = '$IMPFlag'; + + /** + * Singleton instance. + * + * @var IMP_Imap_Flags + */ + static protected $_instance; + + /** + * The cached list of flags. + * + * @var array + */ + protected $_flags = null; + + /** + * Attempts to return a reference to a concrete object instance. + * It will only create a new instance if no instance currently exists. + * + * @return IMP_Imap_Flags The created concrete instance. + */ + static public function singleton() + { + if (!isset(self::$_instance)) { + self::$_instance = new IMP_Imap_Flags(); + } + + return self::$_instance; + } + + /** + * Save the flag list to the prefs backend. + */ + protected function _save() + { + $GLOBALS['prefs']->setValue('msgflags', json_encode($this->_flags)); + } + + /** + * Return the raw list of flags. + * + * @param array $options Additional options: + *
+     * 'div' - (boolean) If true, return a DIV tag containing the code
+     *         necessary to display the icon.
+     *         DEFAULT: false
+     * 'fgcolor' - (boolean) If true, add foreground color information to be
+     *             used for text overlay purposes.
+     *             DEFAULT: false
+     * 'imap' - (boolean) If true, only return IMAP flags.
+     *          DEFAULT: false
+     * 'mailbox' - (string) A real (not virtual) IMAP mailbox. If set, will
+     *             determine what flags are available in the mailbox.
+     *             DEFAULT: '' (no mailbox check)
+     * 
+ * + * @return array An array of flag information (see 'msgflags' preference + * for format). If 'fgcolor' option is true, also adds + * a 'f' key to each entry with foreground color info. + * If 'div' option is true, adds a 'div' key with HTML + * text. + */ + public function getList($options = array()) + { + $this->_loadList(); + + $def_color = $GLOBALS['prefs']->getValue('msgflags_color'); + $ret = $this->_flags; + $avail_flags = array_keys($ret); + + if (!empty($options['imap'])) { + $types = array('imapp', 'imapu'); + if (!$GLOBALS['prefs']->getValue('msgflags_hidesys')) { + $types[] = 'imap'; + } + } + + /* Reduce the list of flags for the mailbox depending on the return + * from the PERMANENTFLAGS IMAP response. */ + if (!empty($options['mailbox'])) { + try { + $status = $GLOBALS['imp_imap']->ob->status($options['mailbox'], Horde_Imap_Client::STATUS_PERMFLAGS); + if (!in_array('\\*', $status['permflags'])) { + $avail_flags = array_intersect($avail_flags, $status['permflags']); + } + } catch (Horde_Imap_Client_Exception $e) {} + } + + foreach ($avail_flags as $key) { + $ret[$key]['flag'] = $key; + + if (!empty($options['fgcolor'])) { + $ret[$key]['f'] = '#000'; + if (!isset($ret[$key]['b'])) { + $ret[$key]['b'] = $def_color; + } elseif (Horde_Image::brightness($this->_flags[$key]['b']) < 128) { + $ret[$key]['f'] = '#f6f6f6'; + } + } + + if (!empty($options['imap']) && + !in_array($ret[$key]['t'], $types)) { + unset($ret[$key]); + } elseif (!empty($options['div']) && isset($ret[$key]['c'])) { + $ret[$key]['div'] = $this->_getDiv($ret[$key]['c'], $ret[$key]['l']); + } + } + + return $ret; + } + + /** + * Loads the flag list from the preferences into the local cache. + */ + protected function _loadList() + { + if (!is_null($this->_flags)) { + return; + } + + $this->_flags = json_decode($GLOBALS['prefs']->getValue('msgflags'), true); + + /* Sanity checking. */ + if (!is_array($this->_flags)) { + $this->_flags = array(); + $this->_save(); + } + } + + /** + * Add a user-defined IMAP flag. + * + * @param string $label The label to use for the new flag. + * + * @return string The IMAP flag name. + */ + public function addFlag($label) + { + if (strlen($label) == 0) { + return; + } + + $this->_loadList(); + + /* Flags are named PREFIX{$i}. Keep incrementing until we find the + * next available flag. */ + for ($i = 0; ; ++$i) { + $curr = self::PREFIX . $i; + if (!isset($this->_flags[$curr])) { + $this->_flags[$curr] = array( + // 'a' => These flags are not shown in mimp + // TODO: Generate random background + 'b' => '#ffffff', + 'c' => 'flagUser', + 'd' => true, + 'l' => $label, + 't' => 'imapp' + ); + + $this->_save(); + return $curr; + } + } + } + + /** + * Updates a flag. + * + * @param string $label The flag label. + * @param array $data The data to update. + */ + public function updateFlag($label, $data) + { + $this->_loadList(); + + if (isset($this->_flags[$label])) { + foreach ($data as $key => $val) { + $this->_flags[$label][$key] = $val; + } + + $this->_save(); + } + } + + /** + * Delete a flag from the list. + * + * @param string $label The flag label. + * + * @return boolean True on success. + */ + public function deleteFlag($label) + { + $this->_loadList(); + + if (isset($this->_flags[$label]) && + $this->_flags[$label]['l'] && + !empty($this->_flags[$label]['d'])) { + unset($this->_flags[$label]); + $this->_save(); + return true; + } + + return false; + } + + /** + * Parse a list of flag information. + * + * @param array $options Additional options: + *
+     * 'atc' - (Horde_Mime_Part) Attachment info. A Horde_Mime_Part object
+     *         representing the message structure.
+     *         DEFAULT: not parsed
+     * 'div' - (boolean) If true, return a DIV tag containing the code
+     *         necessary to display the icon.
+     *         DEFAULT: false
+     * 'flags' - (array) [REQUIRED] IMAP flag info. A lowercase list of flags
+     *           returned by the IMAP server.
+     * 'personal' - (mixed) Personal message info. Either an array of to
+     *              addresses as returned by
+     *              Horde_Mime_Address::getAddressesFromObject(), or the
+     *              identity that matched the address list..
+     * 'priority' - (string) Message priority. The content of the X-Priority
+     *              header.
+     * 
+ * + * @return array A list of flags with the following keys: + *
+     * 'abbrev' - (string) The abbreviation to use.
+     * 'bg' - (string) The background to use.
+     * 'classname' - (string) If set, the flag classname to use.
+     * 'flag' - (string) The matched flag (lowercase).
+     * 'div' - (string) A DIV HTML element, if 'div' option is true and a
+     *         classname is defined.
+     * 'label' - (string) The label of the flag.
+     * 
+ */ + public function parse($options = array()) + { + $this->_loadList(); + + $process = $ret = array(); + $f = $this->_flags; + + if (isset($options['personal'])) { + if (is_array($options['personal'])) { + require_once 'Horde/Identity.php'; + $identity = Identity::singleton(array('imp', 'imp')); + foreach ($options['personal'] as $val) { + if ($identity->hasAddress($val['inner'])) { + $process['personal'] = $f['personal']; + break; + } + } + } else if (!is_null($options['personal'])) { + $process['personal'] = $f['personal']; + } + } + + if (!empty($options['priority'])) { + $imp_msg_ui = new IMP_UI_Message(); + switch ($imp_msg_ui->getXpriority($options['priority'])) { + case 'high': + $process['highpri'] = $f['highpri']; + break; + + case 'low': + $process['lowpri'] = $f['lowpri']; + break; + } + } + + if (!empty($options['atc'])) { + $imp_mbox_ui = new IMP_UI_Mailbox(); + if ($type = $imp_ui->getAttachmentType($ob['structure']->getType())) { + $process[$type] = $f[$type]; + } + } + + if (($_SESSION['imp']['protocol'] == 'imap') && + isset($options['flags'])) { + if (!empty($options['flags'])) { + $options['flags'] = array_map('strtolower', $options['flags']); + } + + foreach ($f as $k => $v) { + if (in_array($v['t'], array('imap', 'imapp', 'imapu', 'imp'))) { + if (empty($v['n'])) { + $match = in_array($k, $options['flags']); + } elseif (empty($options['flags'])) { + $match = true; + } else { + $match = !in_array($k, $options['flags']); + } + + if ($match) { + $process[$k] = $v; + } + } + } + } + + $def_color = $GLOBALS['prefs']->getValue('msgflags_color'); + + foreach ($process as $key => $val) { + $tmp = array( + 'bg' => isset($val['b']) ? $val['b'] : $def_color, + 'flag' => $key, + 'label' => $val['l'] + ); + + if (isset($val['a'])) { + $tmp['abbrev'] = $val['a']; + } + + if (isset($val['c'])) { + $tmp['classname'] = $val['c']; + if (!empty($options['div'])) { + $tmp['div'] = $this->_getDiv($val['c'], $val['l']); + } + } + + $ret[] = $tmp; + } + + return $ret; + } + + /** + * Output a DIV element to display the icon. + * + * @param string $c A classname. + * @param string $l The flag label. + * + * @return string A HTML DIV element. + */ + protected function _getDiv($c, $l) + { + return '
'; + } + +} diff --git a/imp/lib/Mailbox.php b/imp/lib/Mailbox.php index 5ed12496b..cd86057dd 100644 --- a/imp/lib/Mailbox.php +++ b/imp/lib/Mailbox.php @@ -116,21 +116,49 @@ class IMP_Mailbox * Build the array of message information. * * @param array $msgnum An array of message sequence numbers. - * @param mixed $preview Include preview information? If empty, add no - * preview information. If 1, uses value from - * prefs. If 2, forces addition of preview info. - * @param array $headers A list of non-standard (non-envelope) headers to - * return. + * @param array $options Additional options: + *
+     * 'headers' - (array) A list of non-standard (non-envelope) headers to
+     *             return.
+     *             DEFAULT: Only envelope headers returned.
+     * 'preview' - (mixed) Include preview information?  If empty, add no
+     *                     preview information. If 1, uses value from prefs.
+     *                     If 2, forces addition of preview info.
+     *                     DEFAULT: No preview information.
+     * 'structure' - (boolean) Get structure information from server.
+     *               Contained in the 'strucutre' entry.
+     *               DEFAULT: false
+     * 
* * @return array An array with the following keys: *
-     * 'overview' - (array) The overview information.
+     * 'overview' - (array) The overview information. Contains the following:
+     *              'envelope' - (array) Envelope information returned from
+     *                           the IMAP server. See
+     *                           Horde_Imap_Client::fetch() for format.
+     *              'flags' - (array) The list of IMAP flags returned from
+     *                        the server. See Horde_Imap_Client::fetch() for
+     *                        the format.
+     *              'headers' - (array) Any headers requested in
+     *                          $options['headers']. Horde_Mime_Headers objects
+     *                          are returned.  See Horde_Imap_Client::fetch()
+     *                          for the format.
+     *              'mailbox' - (string) The mailbox containing the message.
+     *              'preview' - (string) If requested in $options['preview'],
+     *                          the preview text.
+     *              'previewcut'- (boolean) Has the preview text been cut?
+     *              'seq' - (integer) The sequence number of the message.
+     *              'size' - (integer) The size of the message in bytes.
+     *              'structure'- (array) The structure of the message. Only
+     *                           set if $options['structure'] is true. See
+     *                           Horde_Imap_Client::fetch() for format.
+     *              'uid' - (string) The unique ID of the message.
+     *
      * 'uids' - (array) The array of UIDs. It is in the same format as used
      *          for IMP::parseIndicesList().
      * 
*/ - public function getMailboxArray($msgnum, $preview = false, - $headers = array()) + public function getMailboxArray($msgnum, $options = array()) { $this->_buildMailbox(); @@ -159,18 +187,27 @@ class IMP_Mailbox Horde_Imap_Client::FETCH_SEQ => true ); - if (!empty($headers)) { - $fetch_criteria[Horde_Imap_Client::FETCH_HEADERS] = array(array('headers' => $headers, 'label' => 'imp', 'parse' => true, 'peek' => true)); + if (!empty($options['headers'])) { + $fetch_criteria[Horde_Imap_Client::FETCH_HEADERS] = array(array('headers' => $options['headers'], 'label' => 'imp', 'parse' => true, 'peek' => true)); } - $cache = $preview ? $GLOBALS['imp_imap']->ob->getCache() : null; + if (!empty($options['structure'])) { + $fetch_criteria[Horde_Imap_Client::FETCH_STRUCTURE] = array('parse' => true); + } + + if (empty($options['preview'])) { + $cache = null; + $options['preview'] = 0; + } else { + $cache = $GLOBALS['imp_imap']->ob->getCache(); + } /* Retrieve information from each mailbox. */ foreach ($to_process as $mbox => $ids) { try { $fetch_res = $GLOBALS['imp_imap']->ob->fetch($mbox, $fetch_criteria, array('ids' => array_keys($ids))); - if ($preview) { + if ($options['preview']) { $preview_info = $tostore = array(); if ($cache) { try { @@ -186,10 +223,9 @@ class IMP_Mailbox $v['headers'] = $v['headers']['imp']; } - if ($preview && - (($preview === 2) || - !$GLOBALS['prefs']->getValue('preview_show_unread') || - !in_array('\\seen', $v['flags']))) { + if (($options['preview'] === 2) || + !$GLOBALS['prefs']->getValue('preview_show_unread') || + !in_array('\\seen', $v['flags'])) { if (empty($preview_info[$k])) { try { $imp_contents = IMP_Contents::singleton($k . IMP::IDX_SEP . $mbox); diff --git a/imp/lib/UI/Mailbox.php b/imp/lib/UI/Mailbox.php index 4a5c36728..5532765ba 100644 --- a/imp/lib/UI/Mailbox.php +++ b/imp/lib/UI/Mailbox.php @@ -159,23 +159,10 @@ class IMP_UI_Mailbox } /** - * The list of ALT text to use for mailbox display icons. - * - * @return array Type -> ALT text mappings. - */ - static public function getAttachmentAltList() - { - return array( - 'signed' => _("Message is signed"), - 'encrypted' => _("Message is encrypted"), - 'attachment' => _("Message has attachments") - ); - } - - /** * Return the icon to use for a given attachment. * - * @return string The mailbox display icon type. + * @return string The mailbox display icon type (attach, encrypt, + * signed). */ public function getAttachmentType($type) { @@ -186,7 +173,7 @@ class IMP_UI_Mailbox return 'signed'; case 'encrypted': - return 'encrypted'; + return 'encrypt'; case 'alternative': case 'related': @@ -194,10 +181,10 @@ class IMP_UI_Mailbox break; default: - return 'attachment'; + return 'attach'; } } elseif ($type == 'application/pkcs7-mime') { - return 'encrypted'; + return 'encrypt'; } return ''; diff --git a/imp/lib/Views/ListMessages.php b/imp/lib/Views/ListMessages.php index ef0baa8da..9dc9ade89 100644 --- a/imp/lib/Views/ListMessages.php +++ b/imp/lib/Views/ListMessages.php @@ -262,11 +262,11 @@ class IMP_Views_ListMessages * to process. * @param boolean $search Is this a search mbox? * - * @return array TODO + * @return array TODO */ private function _getOverviewData($imp_mailbox, $folder, $msglist, $search) { - $lookup = $msgs = array(); + $msgs = array(); if (empty($msglist)) { return $msgs; @@ -275,7 +275,7 @@ class IMP_Views_ListMessages require_once 'Horde/Identity.php'; /* Get mailbox information. */ - $overview = $imp_mailbox->getMailboxArray($msglist, false, array('list-post')); + $overview = $imp_mailbox->getMailboxArray($msglist, array('headers' => array('list-post', 'x-priority'), 'structure' => $GLOBALS['prefs']->getValue('atc_flag'))); $charset = NLS::getCharset(); $imp_ui = new IMP_UI_Mailbox($folder); @@ -284,6 +284,8 @@ class IMP_Views_ListMessages while (list(,$ob) = each($overview['overview'])) { /* Initialize the header fields. */ $msg = array( + 'bg' => array('msgRow'), + 'flag' => array(), 'imapuid' => $ob['uid'], 'menutype' => 'message', 'rownum' => $ob['seq'], @@ -291,31 +293,27 @@ class IMP_Views_ListMessages ); /* Get all the flag information. */ - $bg = array('msgRow'); - if ($_SESSION['imp']['protocol'] != 'pop') { - if (!in_array('\\seen', $ob['flags'])) { - $bg[] = 'unseen'; - } - if (in_array('\\answered', $ob['flags'])) { - $bg[] = 'answered'; - } - if (in_array('\\draft', $ob['flags'])) { - $bg[] = 'draft'; - $msg['menutype'] = 'draft'; - $msg['draft'] = 1; - } - if (in_array('\\flagged', $ob['flags'])) { - $bg[] = 'flagged'; - } - if (in_array('\\deleted', $ob['flags'])) { - $bg[] = 'deletedmsg'; - } - if (in_array('$forwarded', $ob['flags'])) { - $bg[] = 'forwarded'; - } + if (!empty($GLOBALS['conf']['hooks']['msglist_flags'])) { + $ob['flags'] = array_merge($ob['flags'], Horde::callHook('_imp_hook_msglist_flags', array($ob, 'dimp'), 'imp')); } - $msg['bg'] = $bg; + $imp_flags = &IMP_Imap_Flags::singleton(); + $flag_parse = $imp_flags->parse(array( + 'atc' => isset($ob['structure']) ? $ob['structure'] : null, + 'flags' => $ob['flags'], + 'personal' => Horde_Mime_Address::getAddressesFromObject($ob['envelope']['to']), + 'priority' => $ob['headers']->getValue('x-priority') + )); + + foreach ($flag_parse as $val) { + $msg['flag'][] = $val['flag']; + } + + /* Specific flag checking. */ + if (in_array('\\draft', $ob['flags'])) { + $msg['menutype'] = 'draft'; + $msg['draft'] = 1; + } /* Format size information. */ $msg['size'] = htmlspecialchars($imp_ui->getSize($ob['size']), ENT_QUOTES, $charset); @@ -345,39 +343,11 @@ class IMP_Views_ListMessages } else { $msgs[$ob['uid']] = $msg; } - - if (!isset($lookup[$ob['mailbox']])) { - $lookup[$ob['mailbox']] = array(); - } - $lookup[$ob['mailbox']][] = $ob['uid']; - } - - /* Add user supplied information from hook. */ - if (!empty($GLOBALS['conf']['imp']['hooks']['msglist_format'])) { - foreach (array_keys($lookup) as $mbox) { - $ob_f = Horde::callHook('_imp_hook_msglist_format', array($mbox, $lookup[$mbox], 'dimp'), 'imp'); - - foreach ($ob_f as $uid => $val) { - if ($search) { - $ptr = &$msgs[$uid . $mbox]; - } else { - $ptr = &$msgs[$uid]; - } - - if (!empty($val['atc'])) { - $ptr['atc'] = $val['atc']; - } - - if (!empty($val['class'])) { - $ptr['bg'] = array_merge($ptr['bg'], $val['class']); - } - } - } } /* Allow user to alter template array. */ - if (!empty($GLOBALS['conf']['imp']['dimp']['hooks']['mailboxarray'])) { - $msgs = Horde::callHook('_imp_hook_dimp_mailboxarray', array($msgs), 'imp'); + if (!empty($GLOBALS['conf']['imp']['hooks']['mailboxarray'])) { + $msgs = Horde::callHook('_imp_hook_mailboxarray', array($msgs, 'dimp'), 'imp'); } return $msgs; diff --git a/imp/lib/Views/ShowMessage.php b/imp/lib/Views/ShowMessage.php index bb71a4991..1ddb19545 100644 --- a/imp/lib/Views/ShowMessage.php +++ b/imp/lib/Views/ShowMessage.php @@ -101,16 +101,9 @@ class IMP_Views_ShowMessage /* Set the current time zone. */ NLS::setTimeZone(); - /* Get envelope/flag/header information. */ + /* Get envelope/header information. We don't use flags in this + * view. */ try { - $flags_ret = $GLOBALS['imp_imap']->ob->fetch($folder, array( - Horde_Imap_Client::FETCH_FLAGS => true, - ), array('ids' => array($index))); - if (!isset($flags_ret[$index])) { - $result['error'] = $error_msg; - $result['errortype'] = 'horde.error'; - return $result; - } $fetch_ret = $GLOBALS['imp_imap']->ob->fetch($folder, array( Horde_Imap_Client::FETCH_ENVELOPE => true, Horde_Imap_Client::FETCH_HEADERTEXT => array(array('parse' => true, 'peek' => false)) diff --git a/imp/lib/prefs.php b/imp/lib/prefs.php index 6df19415d..794a7b8dc 100644 --- a/imp/lib/prefs.php +++ b/imp/lib/prefs.php @@ -158,6 +158,53 @@ function handle_soundselect($updated) return $GLOBALS['prefs']->setValue('nav_audio', Util::getFormData('nav_audio')); } +function handle_flagmanagement($updated) +{ + $imp_flags = &IMP_Imap_Flags::singleton(); + $flag_action = Util::getFormData('flag_action'); + $flag_data = Util::getFormData('flag_data'); + + if ($flag_action == 'add') { + $imp_flags->addFlag($flag_data); + return false; + } + + $def_color = $GLOBALS['prefs']->getValue('msgflags_color'); + + // Don't set updated on these actions. User may want to do more actions. + foreach ($imp_flags->getList() as $key => $val) { + $md5 = hash('md5', $key); + + switch ($flag_action) { + case 'delete': + if ($flag_data == ('bg_' . $md5)) { + $imp_flags->deleteFlag($key); + return false; + } + break; + + default: + /* Change labels for user-defined flags. */ + if ($val['t'] == 'imapp') { + $label = Util::getFormData('label_' . $md5); + if (strlen($label) && ($label != $val['l'])) { + $imp_flags->updateFlag($key, array('l' => $label)); + } + } + + /* Change background for all flags. */ + $bg = strtolower(Util::getFormData('bg_' . $md5)); + if ((isset($val['b']) && ($bg != $val['b'])) || + (!isset($val['b']) && ($bg != $def_color))) { + $imp_flags->updateFlag($key, array('b' => $bg)); + } + break; + } + } + + return false; +} + function prefs_callback() { global $prefs; diff --git a/imp/mailbox-mimp.php b/imp/mailbox-mimp.php index 159fba4f4..fbaa6866e 100644 --- a/imp/mailbox-mimp.php +++ b/imp/mailbox-mimp.php @@ -87,7 +87,7 @@ $sortpref = IMP::getSort($imp_mbox['mailbox']); $imp_ui = new IMP_UI_Mailbox($imp_mbox['mailbox']); /* Build the array of message information. */ -$mbox_info = $imp_mailbox->getMailboxArray(range($pageOb['begin'], $pageOb['end'])); +$mbox_info = $imp_mailbox->getMailboxArray(range($pageOb['begin'], $pageOb['end']), array('headers' => array('x-priority'))); /* Get thread information. */ $threadob = ($sortpref['by'] == Horde_Imap_Client::SORT_THREAD) @@ -98,7 +98,7 @@ reset($mbox_info); while (list(,$ob) = each($mbox_info['overview'])) { /* Initialize the header fields. */ $msg = array( - 'number' => $ob['seq'], + 'subject' => $imp_ui->getSubject($ob['envelope']['subject']), 'status' => '' ); @@ -109,8 +109,6 @@ while (list(,$ob) = each($mbox_info['overview'])) { $msg['from'] = String::substr($msg['from'], 0, $conf['mimp']['mailbox']['max_from_chars']) . '...'; } - $msg['subject'] = $imp_ui->getSubject($ob['envelope']['subject']); - if (!is_null($threadob) && ($threadob->getThreadIndent($ob['uid']))) { $msg['subject'] = '>> ' . ltrim($msg['subject']); } @@ -119,38 +117,25 @@ while (list(,$ob) = each($mbox_info['overview'])) { $msg['subject'] = String::substr($msg['subject'], 0, $conf['mimp']['mailbox']['max_subj_chars']) . '...'; } - /* Generate the target link. */ - $target = IMP::generateIMPUrl('message-mimp.php', $imp_mbox['mailbox'], $ob['uid'], $ob['mailbox']); - /* Get flag information. */ - if ($_SESSION['imp']['protocol'] != 'pop') { - $to_ob = Horde_Mime_Address::getAddressesFromObject($ob['envelope']['to']); - if (!empty($to_ob) && $identity->hasAddress($to_ob[0]['inner'])) { - $msg['status'] .= '+'; - } - if (!in_array('\\seen', $ob['flags'])) { - $msg['status'] .= 'N'; - } - if (in_array('\\answered', $ob['flags'])) { - $msg['status'] .= 'r'; - } - if (in_array('\\draft', $ob['flags'])) { - $target = IMP::composeLink(array(), array('a' => 'd', 'thismailbox' => $imp_mbox['mailbox'], 'index' => $ob['uid'], 'bodypart' => 1)); - } - if (in_array('\\flagged', $ob['flags'])) { - $msg['status'] .= 'I'; - } - if (in_array('\\deleted', $ob['flags'])) { - $msg['status'] .= 'D'; - } - - /* Support for the pseudo-standard '$Forwarded' flag. */ - if (in_array('$forwarded', $ob['flags'])) { - $msg['status'] .= 'F'; + $imp_flags = &IMP_Imap_Flags::singleton(); + $flag_parse = $imp_flags->parse(array( + 'flags' => $ob['flags'], + 'personal' => Horde_Mime_Address::getAddressesFromObject($ob['envelope']['to']), + 'priority' => $ob['headers']->getValue('x-priority') + )); + + foreach ($flag_parse as $val) { + if (isset($val['abbrev'])) { + $msg['status'] .= $val['abbrev']; } } - $msg['target'] = $target; + /* Generate the target link. */ + $msg['target'] = in_array('\\draft', $ob['flags']) + ? IMP::composeLink(array(), array('a' => 'd', 'thismailbox' => $imp_mbox['mailbox'], 'index' => $ob['uid'], 'bodypart' => 1)) + : IMP::generateIMPUrl('message-mimp.php', $imp_mbox['mailbox'], $ob['uid'], $ob['mailbox']); + $msgs[] = $msg; } diff --git a/imp/mailbox.php b/imp/mailbox.php index a01282132..f7c248ac2 100644 --- a/imp/mailbox.php +++ b/imp/mailbox.php @@ -12,28 +12,13 @@ * @author Michael Slusarz */ -function _outputSummaries($msgs, $mbox, &$ids) +function _outputSummaries($msgs, $mbox) { static $template; - if (!empty($GLOBALS['conf']['hooks']['msglist_format'])) { - $ob_f = Horde::callHook('_imp_hook_msglist_format', array($mbox, array_keys($msgs), 'imp'), 'imp'); - - foreach ($ob_f as $uid => $val) { - $ptr = &$msgs[$uid]; - - if (!empty($val['class'])) { - $ptr['bg'] = implode(' ', array_keys(array_flip(array_merge(explode(' ', $ptr['bg']), $val['class'])))); - } - - if (!empty($val['flagbits'])) { - $ids[$ptr['id']] |= $val['flagbits']; - } - - if (!empty($val['status'])) { - $ptr['status'] .= $val['status']; - } - } + /* Allow user to alter template array. */ + if (!empty($GLOBALS['conf']['imp']['hooks']['mailboxarray'])) { + $msgs = Horde::callHook('_imp_hook_mailboxarray', array($msgs, 'imp'), 'imp'); } if (!isset($template)) { @@ -52,6 +37,7 @@ function _outputSummaries($msgs, $mbox, &$ids) echo $template->fetch(IMP_TEMPLATES . '/mailbox/mailbox.html'); } + require_once dirname(__FILE__) . '/lib/base.php'; /* Call the mailbox redirection hook, if requested. */ @@ -92,10 +78,6 @@ if (!is_array(($indices = Util::getFormData('indices')))) { /* Set the current time zone. */ NLS::setTimeZone(); -/* Initialize the user's identities. */ -require_once 'Horde/Identity.php'; -$identity = Identity::singleton(array('imp', 'imp')); - $do_filter = false; $open_compose_window = null; @@ -258,10 +240,14 @@ $imp_mailbox = IMP_Mailbox::singleton($imp_mbox['mailbox']); $pageOb = $imp_mailbox->buildMailboxPage(Util::getFormData('page'), $start); $show_preview = ($conf['mailbox']['show_preview'] && $prefs->getValue('preview_enabled')); -$overview_headers = empty($conf['fetchmail']['show_account_colors']) - ? array() - : array('x-color'); -$mbox_info = $imp_mailbox->getMailboxArray(range($pageOb['begin'], $pageOb['end']), $show_preview, $overview_headers); +$overview_headers = array('x-priority'); +if (empty($conf['fetchmail']['show_account_colors'])) { + $fetchmail = false; +} else { + $fetchmail = true; + $overview_headers[] = 'x-color'; +} +$mbox_info = $imp_mailbox->getMailboxArray(range($pageOb['begin'], $pageOb['end']), array('preview' => $show_preview, 'headers' => $overview_headers, 'structure' => $prefs->getValue('atc_flag'))); /* Determine sorting preferences. */ $sortpref = IMP::getSort(); @@ -460,7 +446,7 @@ if ($_SESSION['imp']['protocol'] != 'pop') { if (!$search_mbox) { $hdr_template->set('search', Horde::link(Util::addParameter(Horde::applicationUrl('search.php'), 'search_mailbox', $imp_mbox['mailbox']), sprintf(_("Search %s"), $rawtitle)) . Horde::img('search.png', _("Search"), '', $graphicsdir) . ''); if (!$readonly) { - $hdr_template->set('empty', Horde::link(Util::addParameter($mailbox_imp_url, array('actionID' => 'empty_mailbox', 'mailbox' => $imp_mbox['mailbox'], 'mailbox_token' => $mailbox_token)), _("Empty folder"), '', '', "ImpMessage.confirmDialog(this.href, '" . addslashes(_("Are you sure you wish to delete all mail in this folder?")) . "'); return false;") . Horde::img('empty_spam.png', _("Empty folder")) . ''); + $hdr_template->set('empty', Horde::link(Util::addParameter($mailbox_imp_url, array('actionID' => 'empty_mailbox', 'mailbox' => $imp_mbox['mailbox'], 'mailbox_token' => $mailbox_token)), _("Empty folder"), '', '', "ImpMailbox.confirmDialog(this.href, '" . addslashes(_("Are you sure you wish to delete all mail in this folder?")) . "'); return false;") . Horde::img('empty_spam.png', _("Empty folder")) . ''); } } else { if ($imp_search->isEditableVFolder()) { @@ -503,6 +489,8 @@ if (empty($pageOb['end'])) { exit; } +$imp_flags = &IMP_Imap_Flags::singleton(); + /* Display the navbar and actions if there is at least 1 message in mailbox. */ if ($pageOb['msgcount']) { $use_trash = $prefs->getValue('use_trash'); @@ -515,21 +503,37 @@ if ($pageOb['msgcount']) { $n_template->set('use_folders', $conf['user']['allow_folders']); $n_template->set('readonly', $readonly); $n_template->set('use_pop', $_SESSION['imp']['protocol'] == 'pop'); - $n_template->set('use_trash', $use_trash); - $n_template->set('imp_all', IMP::FLAG_ALL); - $n_template->set('imp_unseen', IMP::FLAG_UNSEEN); - $n_template->set('imp_flagged', IMP::FLAG_FLAGGED); - $n_template->set('imp_answered', IMP::FLAG_ANSWERED); - $n_template->set('imp_deleted', IMP::FLAG_DELETED); - $n_template->set('imp_draft', IMP::FLAG_DRAFT); - $n_template->set('imp_personal', IMP::FLAG_PERSONAL); - $n_template->set('imp_forwarded', IMP::FLAG_FORWARDED); - if ($n_template->get('use_folders')) { - $n_template->set('move', Horde::widget('#', _("Move to folder"), 'widget moveAction', '', '', _("Move"), true)); - $n_template->set('copy', Horde::widget('#', _("Copy to folder"), 'widget copyAction', '', '', _("Copy"), true)); - $n_template->set('folder_options', $folder_options); + if (!$n_template->get('use_pop')) { + $flaglist_set = $flaglist_unset = array(); + foreach ($imp_flags->getList(array('imap' => true, 'mailbox' => $search_mbox ? null : $imp_mbox['mailbox'])) as $val) { + $tmp = array( + 'f' => $val['flag'], + 'l' => $val['l'] + ); + + /* These entries are 'opposite' flag actions. */ + if (isset($val['n'])) { + $flaglist_unset[] = $tmp; + $tmp['f'] = '0' . $val['flag']; + $flaglist_set[] = $tmp; + } else { + $flaglist_set[] = $tmp; + $tmp['f'] = '0' . $val['flag']; + $flaglist_unset[] = $tmp; + } + } + + $n_template->set('flaglist_set', $flaglist_set); + $n_template->set('flaglist_unset', $flaglist_unset); + + if ($n_template->get('use_folders')) { + $n_template->set('move', Horde::widget('#', _("Move to folder"), 'widget moveAction', '', '', _("Move"), true)); + $n_template->set('copy', Horde::widget('#', _("Copy to folder"), 'widget copyAction', '', '', _("Copy"), true)); + $n_template->set('folder_options', $folder_options); + } } + $n_template->set('mailbox_url', $mailbox_url); $n_template->set('mailbox', htmlspecialchars($imp_mbox['mailbox'])); if ($pageOb['pagecount'] > 1) { @@ -708,18 +712,18 @@ if ($pageOb['msgcount']) { } } -/* Cache some repetitively used variables. */ +/* Initialize repetitively used variables. */ $fromlinkstyle = $prefs->getValue('from_link'); $imp_ui = new IMP_UI_Mailbox($imp_mbox['mailbox']); /* Display message information. */ -$ids = $msgs = array(); +$msgs = array(); $search_template = null; while (list(,$ob) = each($mbox_info['overview'])) { if ($search_mbox) { if (empty($lastMbox) || ($ob['mailbox'] != $lastMbox)) { if (!empty($lastMbox)) { - _outputSummaries($msgs, $lastMbox, $ids); + _outputSummaries($msgs, $lastMbox); $msgs = array(); } $folder_link = Horde::url(Util::addParameter('mailbox.php', 'mailbox', $ob['mailbox'])); @@ -741,15 +745,16 @@ while (list(,$ob) = each($mbox_info['overview'])) { $lastMbox = $ob['mailbox']; - /* Initialize the header fields. */ + /* Initialize the data fields. */ $msg = array( 'bg' => '', + 'class' => '', 'color' => '', 'date' => htmlspecialchars($imp_ui->getDate($ob['envelope']['date'])), 'number' => $ob['seq'], 'preview' => '', - 'size' => htmlspecialchars($imp_ui->getSize($ob['size'])), 'status' => '', + 'size' => htmlspecialchars($imp_ui->getSize($ob['size'])), 'uid' => htmlspecialchars($ob['uid'] . IMP::IDX_SEP . $ob['mailbox']), ); @@ -762,58 +767,34 @@ while (list(,$ob) = each($mbox_info['overview'])) { $target = IMP::generateIMPUrl('message.php', $imp_mbox['mailbox'], $ob['uid'], $ob['mailbox']); /* Get all the flag information. */ - $bg = array(); - $flagbits = 0; - - $to_ob = Horde_Mime_Address::getAddressesFromObject($ob['envelope']['to']); - if (!empty($to_ob) && $identity->hasAddress($to_ob[0]['inner'])) { - $msg['status'] .= Horde::img('mail_personal.png', _("Personal"), array('title' => _("Personal"))); - $flagbits |= IMP::FLAG_PERSONAL; + if (!empty($GLOBALS['conf']['hooks']['msglist_flags'])) { + $ob['flags'] = array_merge($ob['flags'], Horde::callHook('_imp_hook_msglist_flags', array($ob, 'imp'), 'imp')); } - if ($_SESSION['imp']['protocol'] != 'pop') { - if (!in_array('\\seen', $ob['flags'])) { - $flagbits |= IMP::FLAG_UNSEEN; - $msg['status'] .= Horde::img('mail_unseen.png', _("Unseen"), array('title' => _("Unseen"))); - $bg[] = 'unseen'; - } else { - $bg[] = 'seen'; - } - if (in_array('\\answered', $ob['flags'])) { - $flagbits |= IMP::FLAG_ANSWERED; - $msg['status'] .= Horde::img('mail_answered.png', _("Answered"), array('title' => _("Answered"))); - $bg[] = 'answered'; - } - if (in_array('\\draft', $ob['flags'])) { - $flagbits |= IMP::FLAG_DRAFT; - $msg['status'] .= Horde::img('mail_draft.png', _("Draft"), array('title' => _("Draft"))); - $target = IMP::composeLink(array(), array('actionID' => 'draft', 'thismailbox' => $ob['mailbox'], 'index' => $ob['uid'])); - } - if (in_array('\\flagged', $ob['flags'])) { - $flagbits |= IMP::FLAG_FLAGGED; - $msg['status'] .= Horde::img('mail_flagged.png', _("Flagged For Followup"), array('title' => _("Flagged For Followup"))); - $bg[] = 'flagged'; - } - if (in_array('\\deleted', $ob['flags'])) { - $flagbits |= IMP::FLAG_DELETED; - $msg['status'] .= Horde::img('mail_deleted.png', _("Deleted"), array('title' => _("Deleted"))); - $bg[] = 'deleted'; - } + $flag_parse = $imp_flags->parse(array( + 'atc' => isset($ob['structure']) ? $ob['structure'] : null, + 'div' => true, + 'flags' => $ob['flags'], + 'personal' => Horde_Mime_Address::getAddressesFromObject($ob['envelope']['to']), + 'priority' => $ob['headers']->getValue('x-priority') + )); - /* Support for the pseudo-standard '$Forwarded' flag. */ - if (in_array('$forwarded', $ob['flags'])) { - $flagbits |= IMP::FLAG_FORWARDED; - $msg['status'] .= Horde::img('mail_forwarded.png', _("Forwarded"), array('title' => _("Forwarded"))); - $bg[] = 'forwarded'; + foreach ($flag_parse as $val) { + if (isset($val['div'])) { + $msg['status'] .= $val['div']; + } + if (isset($val['classname'])) { + $msg['class'] = $val['classname']; } + $msg['bg'] = $val['bg']; } - $ids[$msg['id']] = $flagbits; - $msg['bg'] = implode(' ', $bg); - /* Show colors for fetchmail messages? */ - if (!empty($ob['headers']['x-color'])) { - $msg['color'] = htmlspecialchars($color); + if ($fetchmail) { + $color = $ob['headers']->getValue('x-color'); + if ($color) { + $msg['color'] = htmlspecialchars($color); + } } /* Show message preview? */ @@ -862,9 +843,9 @@ while (list(,$ob) = each($mbox_info['overview'])) { /* Format the Subject: Header. */ $msg['subject'] = $imp_ui->getSubject($ob['envelope']['subject'], true); if ($preview_tooltip) { - $msg['subject'] = substr(Horde::linkTooltip($target, $msg['preview'], '', '', '', $msg['preview']), 0, -1) . ' id="subject' . $msg['id'] . '">' . $msg['subject'] . ''; + $msg['subject'] = substr(Horde::linkTooltip($target, $msg['preview'], '', '', '', $msg['preview']), 0, -1) . ' class="mboxSubject">' . $msg['subject'] . ''; } else { - $msg['subject'] = substr(Horde::link($target, $msg['preview']), 0, -1) . ' id="subject' . $msg['id'] . '">' . $msg['subject'] . '' . (!empty($msg['preview']) ? '
' . $msg['preview'] . '' : ''); + $msg['subject'] = substr(Horde::link($target, $msg['preview']), 0, -1) . ' class="mboxSubject">' . $msg['subject'] . '' . (!empty($msg['preview']) ? '
' . $msg['preview'] . '' : ''); } /* Set up threading tree now. */ @@ -877,7 +858,7 @@ while (list(,$ob) = each($mbox_info['overview'])) { $msgs[$ob['uid']] = $msg; } -_outputSummaries($msgs, $lastMbox, $ids); +_outputSummaries($msgs, $lastMbox); /* Prepare the message footers template. */ $mf_template = new IMP_Template(); @@ -894,9 +875,8 @@ if (($pageOb['end'] - $pageOb['begin']) >= 20) { } IMP::addInlineScript(array( - 'ImpMessage.messagelist = ' . Horde_Serialize::serialize($ids, Horde_Serialize::JSON, NLS::getCharset()), - 'ImpMessage.sortlimit = ' . intval($sortpref['limit']), - 'ImpMessage.unread = ' . strval($unread) + 'ImpMailbox.sortlimit = ' . intval($sortpref['limit']), + 'ImpMailbox.unread = ' . strval($unread) )); require $registry->get('templates', 'horde') . '/common-footer.inc'; diff --git a/imp/message-mimp.php b/imp/message-mimp.php index bf7f027cd..beedafafb 100644 --- a/imp/message-mimp.php +++ b/imp/message-mimp.php @@ -108,7 +108,7 @@ try { $envelope = $fetch_ret[$index]['envelope']; $flags = $flags_ret[$index]['flags']; -$mime_headers = $fetch_ret[$index]['headertext'][0]; +$mime_headers = reset($fetch_ret[$index]['headertext']); $use_pop = ($_SESSION['imp']['protocol'] == 'pop'); /* Parse the message. */ @@ -206,35 +206,24 @@ case 'low': /* Set the status information of the message. */ $status = ''; -$identity = null; -if (!$use_pop) { - if (!empty($msgAddresses)) { - $identity = $user_identity->getMatchingIdentity($msgAddresses); - if (!is_null($identity) || - ($user_identity->getMatchingIdentity($msgAddresses, false) !== null)) { - $status .= '+'; - } - if (is_null($identity)) { - $identity = $user_identity->getDefault(); - } - } +$match_identity = $identity = null; - /* Set status flags. */ - if (!in_array('\\seen', $flags)) { - $status .= 'N'; +if (!empty($msgAddresses)) { + $match_identity = $identity = $user_identity->getMatchingIdentity($msgAddresses); + if (is_null($identity)) { + $identity = $user_identity->getDefault(); } - $flag_array = array( - '\\answered' => 'r', - '\\draft' => 'D', - '\\flagged' => '!', - '\\deleted' => 'd', - /* Support for the pseudo-standard '$Forwarded' flag. */ - '$forwarded' => 'F' - ); - foreach ($flag_array as $flag => $desc) { - if (in_array($flag, $flags)) { - $status .= $desc; - } +} + +$imp_flags = &IMP_Imap_Flags::singleton(); +$flag_parse = $imp_flags->parse(array( + 'flags' => $flags, + 'personal' => $match_identity +)); + +foreach ($flag_parse as $val) { + if (isset($val['abbrev'])) { + $status .= $val['abbrev']; } } diff --git a/imp/message.php b/imp/message.php index 337596aa7..76778d380 100644 --- a/imp/message.php +++ b/imp/message.php @@ -146,12 +146,11 @@ case 'notspam_report': case 'flag_message': $flag = Util::getFormData('flag'); - if ($flag) { + if ($flag && !empty($indices_array)) { + $set = true; if ($flag[0] == '0') { $flag = substr($flag, 1); $set = false; - } else { - $set = true; } $imp_message->flag(array($flag), $indices_array, $set); if ($prefs->getValue('mailbox_return')) { @@ -301,12 +300,12 @@ $xpriority = $mime_headers->getValue('x-priority'); switch ($imp_ui->getXpriority($xpriority)) { case 'high': $basic_headers['priority'] = _("Priority"); - $display_headers['priority'] = Horde::img('mail_priority_high.png', _("High Priority")) . ' ' . $xpriority; + $display_headers['priority'] = '
' . ' ' . $xpriority; break; case 'low': $basic_headers['priority'] = _("Priority"); - $display_headers['priority'] = Horde::img('mail_priority_low.png', _("Low Priority")) . ' ' . $xpriority; + $display_headers['priority'] = '
' . ' ' . $xpriority; break; } @@ -394,35 +393,25 @@ if (!IMP::$printMode && !empty($conf['maillog']['use_maillog'])) { /* Everything below here is related to preparing the output. */ if (!IMP::$printMode) { /* Set the status information of the message. */ - $identity = $status = null; - if (!$use_pop) { - if (!empty($msgAddresses)) { - $identity = $user_identity->getMatchingIdentity($msgAddresses); - if (($identity !== null) || - $user_identity->getMatchingIdentity($msgAddresses, false) !== null) { - $status .= Horde::img('mail_personal.png', _("Personal"), array('title' => _("Personal"))); - } - if ($identity === null) { - $identity = $user_identity->getDefault(); - } - } + $identity = $match_identity = $status = null; - /* Set status flags. */ - if (!in_array('\\seen', $flags)) { - $status .= Horde::img('mail_unseen.png', _("Unseen"), array('title' => _("Unseen"))); + if (!empty($msgAddresses)) { + $identity = $match_identity = $user_identity->getMatchingIdentity($msgAddresses); + if (is_null($identity)) { + $identity = $user_identity->getDefault(); } - $flag_array = array( - '\\answered' => _("Answered"), - '\\draft' => _("Draft"), - '\\flagged' => _("Flagged For Followup"), - '\\deleted' => _("Deleted"), - /* Support for the pseudo-standard '$Forwarded' flag. */ - '$forwarded' => _("Forwarded") - ); - foreach ($flag_array as $flag => $desc) { - if (in_array($flag, $flags)) { - $status .= Horde::img('mail_' . ltrim($flag, '\\$') . '.png', $desc, array('title' => $desc)); - } + } + + $imp_flags = &IMP_Imap_Flags::singleton(); + $flag_parse = $imp_flags->parse(array( + 'div' => true, + 'flags' => $flags, + 'personal' => $match_identity + )); + + foreach ($flag_parse as $val) { + if (isset($val['div'])) { + $status .= $val['div']; } } @@ -454,10 +443,14 @@ if (!IMP::$printMode) { $n_template->set('usepop', $use_pop); $n_template->set('id', 1); - if ($conf['user']['allow_folders']) { - $n_template->set('move', Horde::widget('#', _("Move to folder"), 'widget moveAction', '', '', _("Move"), true)); - $n_template->set('copy', Horde::widget('#', _("Copy to folder"), 'widget copyAction', '', '', _("Copy"), true)); - $n_template->set('options', IMP::flistSelect(array('heading' => _("This message to"), 'new_folder' => true, 'inc_tasklists' => true, 'inc_notepads' => true))); + if (!$use_pop) { + $n_template->set('flaglist', $imp_flags->getList(array('imap' => true, 'mailbox' => $imp_mbox['mailbox']))); + + if ($conf['user']['allow_folders']) { + $n_template->set('move', Horde::widget('#', _("Move to folder"), 'widget moveAction', '', '', _("Move"), true)); + $n_template->set('copy', Horde::widget('#', _("Copy to folder"), 'widget copyAction', '', '', _("Copy"), true)); + $n_template->set('options', IMP::flistSelect(array('heading' => _("This message to"), 'new_folder' => true, 'inc_tasklists' => true, 'inc_notepads' => true))); + } } $n_template->set('back_to', Horde::widget($mailbox_url, sprintf(_("Back to %s"), $h_page_label), 'widget', '', '', sprintf(_("Bac_k to %s"), $h_page_label), true)); @@ -747,7 +740,6 @@ if (IMP::$printMode) { echo $m_template->fetch(IMP_TEMPLATES . '/message/message.html'); if (!IMP::$printMode) { - echo ''; $a_template->set('isbottom', true); echo $a_template->fetch(IMP_TEMPLATES . '/message/navbar_actions.html'); diff --git a/imp/rss.php b/imp/rss.php index e9b15d72f..0f1825c3a 100644 --- a/imp/rss.php +++ b/imp/rss.php @@ -62,7 +62,7 @@ $ids = $imp_search->runSearchQuery($query, $mailbox, Horde_Imap_Client::SORT_DAT if (!empty($ids)) { $imp_ui = new IMP_UI_Mailbox($imp_mbox['mailbox']); - $overview = $imp_mailbox->getMailboxArray(array_slice($ids, 0, 20), $conf['mailbox']['show_preview'] && $prefs->getValue('preview_enabled')); + $overview = $imp_mailbox->getMailboxArray(array_slice($ids, 0, 20), array('preview' => $conf['mailbox']['show_preview'] && $prefs->getValue('preview_enabled'))); foreach ($overview['overview'] as $ob) { $from_addr = $imp_ui->getFrom($ob['envelope'], array('fullfrom' => true)); diff --git a/imp/templates/index/index-dimp.inc b/imp/templates/index/index-dimp.inc index 3b1f90aaf..04cdd1d22 100644 --- a/imp/templates/index/index-dimp.inc +++ b/imp/templates/index/index-dimp.inc @@ -17,6 +17,10 @@ $sidebar_width = max((int)$prefs->getValue('sidebar_width') - 50, 150) . 'px'; // Quota information $show_quota = (isset($_SESSION['imp']['quota']) && is_array($_SESSION['imp']['quota'])); +// Get the list of available IMAP flags +$imp_flags = &IMP_Imap_Flags::singleton(); +$flag_list = $imp_flags->getList(array('imap' => true)); + // Small utility functions to simplify creating dimpactions buttons. // As of right now, we don't show text only links. function _createDA($text, $image, $id = null, $class = '', $show_text = true) @@ -354,12 +358,15 @@ function _simpleButton($id, $text, $image, $imagedir = null)