From fda1bfe7f1abcc401dfe96ada639c15b74765ea8 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Mon, 12 Apr 2010 21:47:26 -0600 Subject: [PATCH] Ticket #1406: HTML signature support --- imp/compose-dimp.php | 12 ++--- imp/compose.php | 37 ++++--------- imp/config/prefs.php.dist | 14 ++++- imp/docs/CHANGES | 1 + imp/js/compose-base.js | 62 +++++++++++----------- imp/js/compose-dimp.js | 4 +- imp/js/signaturehtml.js | 28 ++++++++++ imp/lib/Ajax/Application.php | 12 ++--- imp/lib/Compose.php | 2 +- imp/lib/Prefs/Identity.php | 94 +++++++++++++++++++++------------- imp/lib/Prefs/Ui.php | 40 +++++++++++++++ imp/lib/Ui/Compose.php | 66 +++++++++++++++++++++++- imp/templates/prefs/signaturehtml.html | 7 +++ 13 files changed, 263 insertions(+), 116 deletions(-) create mode 100644 imp/js/signaturehtml.js create mode 100644 imp/templates/prefs/signaturehtml.html diff --git a/imp/compose-dimp.php b/imp/compose-dimp.php index e9b51b4c0..7f44336c6 100644 --- a/imp/compose-dimp.php +++ b/imp/compose-dimp.php @@ -172,15 +172,13 @@ if ($vars->type == 'redirect') { $imp_ui->attachAutoCompleter(array('to', 'cc', 'bcc', 'redirect_to')); $imp_ui->attachSpellChecker(); - $sig = $identity->getSignature(); + $sig = $identity->getSignature($show_editor ? 'html' : 'text'); if ($get_sig && !empty($sig)) { - if ($show_editor) { - $sig = '

' . $imp_compose->text2html(trim($sig)) . '

'; + if ($identity->getValue('sig_first')) { + $msg = $sig . $msg; + } else { + $msg .= $sig; } - - $msg = ($identity->getValue('sig_first')) - ? "\n" . $sig . $msg - : $msg . "\n" . $sig; } if ($show_editor) { diff --git a/imp/compose.php b/imp/compose.php index 974a5509c..3bdce9f32 100644 --- a/imp/compose.php +++ b/imp/compose.php @@ -616,24 +616,17 @@ if (empty($msg)) { $msg = "\n" . $msg; } -/* Get the current signature. */ -$sig = $identity->getSignature(); - /* Convert from Text -> HTML or vice versa if RTE mode changed. */ if (!is_null($oldrtemode) && ($oldrtemode != $rtemode)) { - if ($rtemode) { - /* Try to find the signature, replace it with a placeholder, - * HTML-ize the message, then replace the signature - * placeholder with the HTML-ized signature, complete with - * marker comment. */ - $msg = preg_replace('/' . preg_replace('/(?text2html($msg); - $msg = str_replace(array('##IMP_SIGNATURE_WS##', '##IMP_SIGNATURE##'), - array('

 

', '

' . $imp_compose->text2html($sig) . '

'), - $msg); - } else { - $msg = Horde_Text_Filter::filter($msg, 'Html2text', array('charset' => Horde_Nls::getCharset(), 'wrap' => false)); + $msg = $imp_ui->convertComposeText($msg, $rtemode ? 'html' : 'text', $identity->getDefault()); +} elseif ($get_sig) { + $sig = $identity->getSignature($rtemode ? 'html' : 'text'); + if (!empty($sig)) { + if ($identity->getValue('sig_first')) { + $msg = $sig . $msg; + } else { + $msg .= "\n" . $sig; + } } } @@ -649,18 +642,6 @@ foreach (array('to', 'cc', 'bcc', 'subject') as $val) { } } -if ($get_sig && isset($msg) && !empty($sig)) { - if ($rtemode) { - $sig = '

 

' . $imp_compose->text2html(trim($sig)) . '

'; - } - - if ($identity->getValue('sig_first')) { - $msg = "\n" . $sig . $msg; - } else { - $msg .= "\n" . $sig; - } -} - /* If PGP encryption is set by default, and we have a recipient list on first * load, make sure we have public keys for all recipients. */ $encrypt_options = $prefs->isLocked('default_encrypt') diff --git a/imp/config/prefs.php.dist b/imp/config/prefs.php.dist index 9507229bf..8f8b3c2b4 100644 --- a/imp/config/prefs.php.dist +++ b/imp/config/prefs.php.dist @@ -13,8 +13,8 @@ $prefGroups['identities'] = array( 'desc' => _("Change the name, address, and signature that people see when they read and reply to your email."), 'members' => array( 'replyto_addr', 'alias_addr', 'tieto_addr', 'bcc_addr', 'signature', - 'sig_dashes', 'sig_first', 'save_sent_mail', 'sent_mail_folder', - 'sentmailselect' + 'signature_html_select', 'sig_dashes', 'sig_first', 'save_sent_mail', + 'sent_mail_folder', 'sentmailselect' ), 'type' => 'identities' ); @@ -54,6 +54,16 @@ $_prefs['signature'] = array( 'desc' => _("Your signature:") ); +// User's HTML signature - UI widget +$_prefs['signature_html_select'] = array( + 'type' => 'special' +); + +// User's HTML signature +$_prefs['signature_html'] = array( + 'value' => '' +); + // precede the signature with dashes ('-- ')? $_prefs['sig_dashes'] = array( 'value' => 0, diff --git a/imp/docs/CHANGES b/imp/docs/CHANGES index 749552b8a..d306acac4 100644 --- a/imp/docs/CHANGES +++ b/imp/docs/CHANGES @@ -2,6 +2,7 @@ v5.0-git -------- +[mms] Added HTML signature support (Request #1406). [mms] Simplified date sorting display (Ticket #8936). [mms] Properly redirect messages pursuant to RFC 5322 [3.6.6]. [mms] Add redirect message capability to DIMP. diff --git a/imp/js/compose-base.js b/imp/js/compose-base.js index e8f837c68..7ec10f28a 100644 --- a/imp/js/compose-base.js +++ b/imp/js/compose-base.js @@ -64,51 +64,49 @@ var IMP_Compose_Base = { replaceSignature: function(id) { - var lastsig, msg, nextsig, oldmsg, pos, - last = this.getIdentity($F('last_identity')), + var lastsig, msg, nextsig, pos, tmp, tmp2, next = this.getIdentity(id); // If the rich text editor is on, we'll use a regexp to find the // signature comment and replace its contents. if (this.editor_on) { - msg = oldmsg = CKEDITOR.instances['composeMessage'].getData().replace(/\r\n/g, '\n'); - - lastsig = '

'; - nextsig = '

' + next.sig.replace(/^ ?
\n/, '').replace(/ +/g, ' ') + '

'; - - // Dot-all functionality achieved with [\s\S], see: - // http://simonwillison.net/2004/Sep/20/newlines/ - msg = msg.replace(/

\s*[\s\S]*?\s*<\/p>/, lastsig); - if (msg == oldmsg) { - msg = nextsig; + // Create a temporary element, import the data from the editor, + // search/replace the current imp signature data, and reinsert + // into the editor. + tmp = new Element('DIV').hide(); + $(document.body).insert(tmp); + tmp.update(CKEDITOR.instances['composeMessage'].getData()); + tmp2 = tmp.select('DIV.impComposeSignature'); + if (tmp2.size()) { + msg = tmp2.last().update(next.sig); + } else { + msg = next.id.sig_loc + ? tmp.insert({ top: next.sig }) + : tmp.insert({ bottom: next.sig }); } + CKEDITOR.instances['composeMessage'].setData(msg.innerHTML); + tmp.remove(); } else { msg = $F('composeMessage').replace(/\r\n/g, '\n'); - + last = this.getIdentity($F('last_identity')); lastsig = last.sig.replace(/^\n/, ''); nextsig = next.sig.replace(/^\n/, ''); - } - - pos = (last.id.sig_loc) - ? msg.indexOf(lastsig) - : msg.lastIndexOf(lastsig); - if (pos != -1) { - if (next.id.sig_loc == last.id.sig_loc) { - msg = msg.substring(0, pos) + nextsig + msg.substring(pos + lastsig.length, msg.length); - } else if (next.id.sig_loc) { - msg = nextsig + msg.substring(0, pos) + msg.substring(pos + lastsig.length, msg.length); - } else { - msg = msg.substring(0, pos) + msg.substring(pos + lastsig.length, msg.length) + nextsig; - } + pos = last.id.sig_loc + ? msg.indexOf(lastsig) + : msg.lastIndexOf(lastsig); - msg = msg.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); - } + if (pos != -1) { + if (next.id.sig_loc == last.id.sig_loc) { + msg = msg.substring(0, pos) + nextsig + msg.substring(pos + lastsig.length, msg.length); + } else if (next.id.sig_loc) { + msg = nextsig + msg.substring(0, pos) + msg.substring(pos + lastsig.length, msg.length); + } else { + msg = msg.substring(0, pos) + msg.substring(pos + lastsig.length, msg.length) + nextsig; + } - if (this.editor_on) { - CKEDITOR.instances['composeMessage'].setData(msg); - } else { - $('composeMessage').setValue(msg); + $('composeMessage').setValue(msg.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n')); + } } $('last_identity').setValue(id); diff --git a/imp/js/compose-dimp.js b/imp/js/compose-dimp.js index 3e6005aa9..f7bb56fbe 100644 --- a/imp/js/compose-dimp.js +++ b/imp/js/compose-dimp.js @@ -337,11 +337,11 @@ var DimpCompose = { this.rte.destroy(); this.RTELoading('show'); - DimpCore.doAction('html2Text', { text: text }, { callback: this.setMessageText.bind(this), ajaxopts: { asynchronous: false } }); + DimpCore.doAction('html2Text', { identity: $F('identity'), text: text }, { callback: this.setMessageText.bind(this), ajaxopts: { asynchronous: false } }); this.RTELoading('hide'); } else { if (!noupdate) { - DimpCore.doAction('text2Html', { text: $F('composeMessage') }, { callback: this.setMessageText.bind(this), ajaxopts: { asynchronous: false } }); + DimpCore.doAction('text2Html', { identity: $F('identity'), text: $F('composeMessage') }, { callback: this.setMessageText.bind(this), ajaxopts: { asynchronous: false } }); } config = Object.clone(IMP.ckeditor_config); diff --git a/imp/js/signaturehtml.js b/imp/js/signaturehtml.js new file mode 100644 index 000000000..b99e622ab --- /dev/null +++ b/imp/js/signaturehtml.js @@ -0,0 +1,28 @@ +/** + * Provides the javascript for managing HTML signature in the preferences UI. + * + * 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 ImpHtmlSignaturePrefs = { + + // Variables defined by other code: ready, sigs + + changeIdentity: function(e) + { + switch (e.memo.pref) { + case 'signature_html_select': + if (this.ready) { + CKEDITOR.instances['signature_html'].setData(this.sigs[e.memo.i]); + } else { + this.changeIdentity.bind(this, e).defer(); + } + break; + } + } + +}; + +CKEDITOR.on('instanceReady', function(e) { ImpHtmlSignaturePrefs.ready = true; }); +document.observe('HordeIdentitySelect:change', ImpHtmlSignaturePrefs.changeIdentity.bindAsEventListener(ImpHtmlSignaturePrefs)); diff --git a/imp/lib/Ajax/Application.php b/imp/lib/Ajax/Application.php index 552e1b10f..d1211b048 100644 --- a/imp/lib/Ajax/Application.php +++ b/imp/lib/Ajax/Application.php @@ -866,10 +866,11 @@ class IMP_Ajax_Application extends Horde_Ajax_Application_Base } /** - * AJAX action: Convert HTML to text. + * AJAX action: Convert HTML to text (compose data). * * Variables used: *

+     * 'identity' - (integer) The current identity.
      * 'text' - (string) The text to convert.
      * 
* @@ -881,18 +882,17 @@ class IMP_Ajax_Application extends Horde_Ajax_Application_Base public function html2Text() { $result = new stdClass; - // Need to replace line endings or else IE won't display line endings - // properly. - $result->text = str_replace("\n", "\r\n", Horde_Text_Filter::filter($this->_vars->text, 'Html2text', array('charset' => Horde_Nls::getCharset()))); + $result->text = $GLOBALS['injector']->getInstance('IMP_Ui_Compose')->convertComposeText($this->_vars->text, 'text', intval($this->_vars->identity)); return $result; } /** - * AJAX action: Convert text to HTML. + * AJAX action: Convert text to HTML (compose data). * * Variables used: *
+     * 'identity' - (integer) The current identity.
      * 'text' - (string) The text to convert.
      * 
* @@ -904,7 +904,7 @@ class IMP_Ajax_Application extends Horde_Ajax_Application_Base public function text2Html() { $result = new stdClass; - $result->text = Horde_Text_Filter::filter($this->_vars->text, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL)); + $result->text = $GLOBALS['injector']->getInstance('IMP_Ui_Compose')->convertComposeText($this->_vars->text, 'html', intval($this->_vars->identity)); return $result; } diff --git a/imp/lib/Compose.php b/imp/lib/Compose.php index 49b4760a6..097aec154 100644 --- a/imp/lib/Compose.php +++ b/imp/lib/Compose.php @@ -2637,7 +2637,7 @@ class IMP_Compose * * @return string HTML text. */ - public function text2html($msg) + static public function text2html($msg) { return Horde_Text_Filter::filter($msg, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL, 'class' => null, 'callback' => null)); } diff --git a/imp/lib/Prefs/Identity.php b/imp/lib/Prefs/Identity.php index eb571dda8..c6281fabd 100644 --- a/imp/lib/Prefs/Identity.php +++ b/imp/lib/Prefs/Identity.php @@ -26,28 +26,25 @@ class Imp_Prefs_Identity extends Horde_Prefs_Identity 'froms' => array(), 'names' => array(), // 'own_addresses' + 'signatures' => array() // 'tie_addresses' ); /** - * Cached signature list. - * - * @var array - */ - protected $_signatures = array(); - - /** * Reads all the user's identities from the prefs object or builds * a new identity from the standard values given in prefs.php. */ public function __construct() { parent::__construct(); + $this->_properties = array_merge( $this->_properties, - array('replyto_addr', 'alias_addr', 'tieto_addr', 'bcc_addr', - 'signature', 'sig_first', 'sig_dashes', 'save_sent_mail', - 'sent_mail_folder') + array( + 'replyto_addr', 'alias_addr', 'tieto_addr', 'bcc_addr', + 'signature', 'signature_html', 'sig_first', 'sig_dashes', + 'save_sent_mail', 'sent_mail_folder' + ) ); } @@ -404,50 +401,77 @@ class Imp_Prefs_Identity extends Horde_Prefs_Identity */ public function getFullname($ident = null) { - if (isset($this->_names[$ident])) { - return $this->_names[$ident]; + if (isset($this->_cached['names'][$ident])) { + return $this->_cached['names'][$ident]; } - $this->_names[$ident] = $this->getValue('fullname', $ident); + $this->_cached['names'][$ident] = $this->getValue('fullname', $ident); - return $this->_names[$ident]; + return $this->_cached['names'][$ident]; } /** * Returns the full signature based on the current settings for the * signature itself, the dashes and the position. * + * @param string $type Either 'text' or 'html'. * @param integer $ident The identity to retrieve the signature from. * * @return string The full signature. * @throws Horde_Exception */ - public function getSignature($ident = null) + public function getSignature($type = 'text', $ident = null) { - if (isset($this->_signatures[$ident])) { - return $this->_signatures[$ident]; + $convert = false; + $key = $ident . '|' . $type; + $val = null; + + if (isset($this->_cached['signatures'][$key])) { + return $this->_cached['signatures'][$key]; } - $val = $this->getValue('signature', $ident); - if (!empty($val)) { - $sig_first = $this->getValue('sig_first', $ident); - $sig_dashes = $this->getValue('sig_dashes', $ident); - $val = str_replace("\r\n", "\n", $val); - if ($sig_dashes) { - $val = "-- \n$val"; + if ($type == 'html') { + $val = $this->getValue('signature_html', $ident); + if (!strlen($val)) { + $convert = true; + $val = null; } - if (isset($sig_first) && $sig_first) { - $val = "\n" . $val . "\n\n\n"; - } else { - $val = "\n" . $val; + } + + if (is_null($val)) { + $val = $this->getValue('signature', $ident); + + if (!empty($val) && ($type == 'text')) { + $sig_first = $this->getValue('sig_first', $ident); + $sig_dashes = $this->getValue('sig_dashes', $ident); + + $val = str_replace("\r\n", "\n", $val); + + if ($sig_dashes) { + $val = "-- \n" . $val . "\n"; + } else { + $val = "\n" . $val; + } + + if ($sig_first) { + $val .= "\n\n\n"; + } + } + } + + if ($val && ($type == 'html')) { + if ($convert) { + $val = IMP_Compose::text2html(trim($val)); } + + $val = '
' . $val . '
'; } try { $val = Horde::callHook('prefs_hook_signature', array($val), 'imp'); } catch (Horde_Exception_HookNotSet $e) {} - $this->_signatures[$ident] = $val; + $this->_cached['signatures'][$key] = $val; return $val; } @@ -455,18 +479,14 @@ class Imp_Prefs_Identity extends Horde_Prefs_Identity /** * Returns an array with the signatures from all identities * + * @param string $type Either 'text' or 'html'. + * * @return array The array with all the signatures. */ - public function getAllSignatures() + public function getAllSignatures($type = 'text') { - static $list; - - if (isset($list)) { - return $list; - } - foreach ($this->_identities as $key => $identity) { - $list[$key] = $this->getSignature($key); + $list[$key] = $this->getSignature($type, $key); } return $list; diff --git a/imp/lib/Prefs/Ui.php b/imp/lib/Prefs/Ui.php index 04c97b9fd..06d6caf4e 100644 --- a/imp/lib/Prefs/Ui.php +++ b/imp/lib/Prefs/Ui.php @@ -121,6 +121,14 @@ class IMP_Prefs_Ui } else { Horde::addScriptFile('folderprefs.js', 'imp'); } + + if ($prefs->isLocked('signature_html') || + empty($_SESSION['imp']['rteavail'])) { + $ui->suppress[] = 'signature_html_select'; + } else { + Horde::addScriptFile('signaturehtml.js', 'imp'); + $GLOBALS['injector']->getInstance('Horde_Editor')->getEditor('Ckeditor', array('id' => 'signature_html')); + } break; case 'logintasks': @@ -309,6 +317,9 @@ class IMP_Prefs_Ui case 'smimepublickey': return $this->_smimePublicKey($ui); + case 'signature_html_select': + return $this->_signatureHtml(); + case 'soundselect': return $this->_sound(); @@ -385,6 +396,9 @@ class IMP_Prefs_Ui $this->_updateSmimePublicKey($ui); return false; + case 'signature_html_select': + return Horde_Prefs_Identity::singleton(array('imp', 'imp'))->setValue('signature_html', $ui->vars->signature_html); + case 'soundselect': return $prefs->setValue('nav_audio', $ui->vars->nav_audio); @@ -1319,6 +1333,32 @@ class IMP_Prefs_Ui } } + /* HTML Signature editing. */ + + /** + * Create code for HTML Signature editing. + * + * @return string HTML UI code. + */ + protected function _signatureHtml() + { + $identity = Horde_Prefs_Identity::singleton(array('imp', 'imp')); + + $js = array(); + foreach (array_keys($identity->getAll('id')) as $key) { + $js[$key] = $identity->getValue('signature_html', $key); + }; + + Horde::addInlineScript(array( + 'ImpHtmlSignaturePrefs.sigs = ' . Horde_Serialize::serialize($js, Horde_Serialize::JSON, Horde_Nls::getCharset()) + )); + + $t = $GLOBALS['injector']->createInstance('Horde_Template'); + $t->setOption('gettext', true); + + return $t->fetch(IMP_TEMPLATES . '/prefs/signaturehtml.html'); + } + /* Sound selection. */ /** diff --git a/imp/lib/Ui/Compose.php b/imp/lib/Ui/Compose.php index f7c6da395..d9ae6e1cb 100644 --- a/imp/lib/Ui/Compose.php +++ b/imp/lib/Ui/Compose.php @@ -273,12 +273,14 @@ class IMP_Ui_Compose $identities = array(); $identity = Horde_Prefs_Identity::singleton(array('imp', 'imp')); + $html_sigs = $identity->getAllSignatures('html'); + foreach ($identity->getAllSignatures() as $ident => $sig) { $identities[] = array( // Plain text signature 'sig' => $sig, // HTML signature - 'sig_html' => str_replace(' target="_blank"', '', Horde_Text_Filter::filter($sig, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL, 'class' => null, 'callback' => null))), + 'sig_html' => $html_sigs[$ident], // Signature location 'sig_loc' => (bool)$identity->getValue('sig_first', $ident), // Sent mail folder name @@ -295,5 +297,67 @@ class IMP_Ui_Compose return 'IMP_Compose_Base.identities = ' . Horde_Serialize::serialize($identities, Horde_Serialize::JSON); } + /** + * Convert compose data to/from text/HTML. + * + * @param string $data The message text. + * @param string $to Either 'text' or 'html'. + * @param integer $identity The current identity. + * + * @return string The converted text + */ + public function convertComposeText($data, $to, $identity) + { + $imp_identity = Horde_Prefs_Identity::singleton(array('imp', 'imp')); + $replaced = 0; + + $html_sig = $imp_identity->getSignature('html', $identity); + $txt_sig = $imp_identity->getSignature('text', $identity); + + /* Try to find the signature, replace it with a placeholder, convert + * the message, and then re-add the signature in the new format. */ + switch ($to) { + case 'html': + if ($txt_sig) { + $data = preg_replace('/' . preg_replace('/(?query("//div[@class='impComposeSignature']"); + $node = $entries->item(0); + $node->parentNode->replaceChild($doc->createTextNode('###IMP_SIGNATURE###'), $node); + $replaced = 1; + + $data = ''; + foreach ($doc->getElementsByTagName('body')->item(0)->childNodes as $node) { + $data .= $doc->saveXML($node); + } + } + + $data = Horde_Text_Filter::filter($data, 'Html2text', array('charset' => Horde_Nls::getCharset(), 'wrap' => false)); + $sig = $txt_sig; + break; + } + + if ($replaced) { + return str_replace('###IMP_SIGNATURE###', $sig, $data); + } elseif ($imp_identity->getValue('sig_first', $identity)) { + return $sig . $data; + } else { + return $msg . "\n" . $sig; + } + } } diff --git a/imp/templates/prefs/signaturehtml.html b/imp/templates/prefs/signaturehtml.html new file mode 100644 index 000000000..37fb8e8b0 --- /dev/null +++ b/imp/templates/prefs/signaturehtml.html @@ -0,0 +1,7 @@ +
+ Your signature to use when composing with the HTML editor (if empty, the text signature will be used): +
+ +
+ +
-- 2.11.0