5.1.5
5.0.0
@@ -39,6 +39,7 @@
LGPL-2
+* [mms] Add horde-remove-user-data command-line script.
* [mms] Add additional syntax checking for the configuration files in the test script.
* [mms] Only update topbar if the underlying tree data has changed.
@@ -123,6 +124,7 @@
+
@@ -130,12 +132,14 @@
+
+
@@ -1935,10 +1939,13 @@
+
+
+
@@ -3766,9 +3773,10 @@
stable
stable
- 2013-10-09
+ 2013-10-19
LGPL-2
+* [mms] Add horde-remove-user-data command-line script.
* [mms] Add additional syntax checking for the configuration files in the test script.
* [mms] Only update topbar if the underlying tree data has changed.
diff --git a/imp/basic.php b/imp/basic.php
index 99bc14f3fbc..ce0b0858efe 100644
--- a/imp/basic.php
+++ b/imp/basic.php
@@ -33,8 +33,12 @@
try {
$ob = new $class($vars);
} catch (Exception $e) {
- $notification->push($e);
- $ob = new IMP_Basic_Error($vars);
+ if ($registry->getView() == $registry::VIEW_BASIC) {
+ $notification->push($e);
+ $ob = new IMP_Basic_Error($vars);
+ } else {
+ throw $e;
+ }
}
$status = $ob->status();
diff --git a/imp/config/hooks.php.dist b/imp/config/hooks.php.dist
index 9aa183e62f2..97666d56f32 100644
--- a/imp/config/hooks.php.dist
+++ b/imp/config/hooks.php.dist
@@ -161,6 +161,28 @@ class IMP_Hooks
}
+ /**
+ * Check compose e-mail recipient before sending.
+ *
+ * @param Horde_Mail_Rfc822_Object $addr The address object.
+ *
+ * @return mixed If an error, an array with the following possible keys:
+ * - msg: (string) The error text.
+ * - level: (string; OPTIONAL) Either 'bad' (DEFAULT) or 'warn'.
+ */
+// public function compose_addr(Horde_Mail_Rfc822_Object $addr)
+// {
+// // Example: Only allow sending to local addresses.
+// if (($addr instanceof Horde_Mail_Rfc822_Address) &&
+// ($addr->host != 'example.com')) {
+// return array(
+// 'msg' => 'Can only send to addresses at example.com.',
+// 'level' => 'bad'
+// );
+// }
+// }
+
+
/**
* Checks the raw text of the outoging compose message for words that
* might indicate an attachment is present, and issues a warning if no
diff --git a/imp/docs/CHANGES b/imp/docs/CHANGES
index 785761609bf..57e85896ba0 100644
--- a/imp/docs/CHANGES
+++ b/imp/docs/CHANGES
@@ -2,6 +2,8 @@
v6.2.0-git
----------
+[mms] Better reporting of e-mail address errors when composing.
+[mms] Add list information display to dynamic view.
[jan] Display signature in compose view.
[mms] Add keyboard shortcuts to allow a more granular scroll of the dynamic
preview pane (Request #12750).
diff --git a/imp/docs/UPGRADING b/imp/docs/UPGRADING
index 7f8ae4ebb10..9ce17848225 100644
--- a/imp/docs/UPGRADING
+++ b/imp/docs/UPGRADING
@@ -51,6 +51,14 @@ The following options have been removed::
$conf['compose']['ac_threshold']
+Hooks (hooks.php)
+-----------------
+
+The following hooks have been added::
+
+ compose_addr
+
+
Preferences (prefs.php)
-----------------------
diff --git a/imp/js/dimpbase.js b/imp/js/dimpbase.js
index 11a353afb6e..3247ad39af1 100644
--- a/imp/js/dimpbase.js
+++ b/imp/js/dimpbase.js
@@ -1460,6 +1460,7 @@ var DimpBase = {
case 'ctx_preview':
[ $('ctx_preview_allparts') ].invoke(this.pp.hide_all ? 'hide' : 'show');
[ $('ctx_preview_thread') ].invoke(this.viewport.getMetaData('nothread') ? 'hide' : 'show');
+ [ $('ctx_preview_listinfo') ].invoke(this.viewport.getSelected().get('dataob').first().listmsg ? 'show' : 'hide');
break;
case 'ctx_template':
@@ -2657,13 +2658,14 @@ var DimpBase = {
clickHandler: function(e)
{
var tmp,
- elt = e.element();
+ elt = e.element(),
+ id = elt.readAttribute('id');
if (DimpCore.DMenu.operaCheck(e.memo)) {
return;
}
- switch (elt.readAttribute('id')) {
+ switch (id) {
case 'imp-normalmboxes':
case 'imp-specialmboxes':
this._handleMboxMouseClick(e.memo);
@@ -2772,7 +2774,7 @@ var DimpBase = {
case 'msgloglist_toggle':
case 'partlist_toggle':
- tmp = (elt.readAttribute('id') == 'partlist_toggle') ? 'partlist' : 'msgloglist';
+ tmp = (id == 'partlist_toggle') ? 'partlist' : 'msgloglist';
$(tmp + '_col', tmp + '_exp').invoke('toggle');
Effect.toggle(tmp, 'blind', {
duration: 0.2,
@@ -2809,8 +2811,9 @@ var DimpBase = {
});
break;
+ case 'ctx_preview_listinfo':
case 'ctx_preview_thread':
- HordeCore.popupWindow(DimpCore.conf.URI_THREAD, {
+ HordeCore.popupWindow((id == 'ctx_preview_listinfo') ? DimpCore.conf.URI_LISTINFO : DimpCore.conf.URI_THREAD, {
buid: this.pp.VP_id,
mailbox: this.pp.VP_view
}, {
@@ -3334,7 +3337,11 @@ var DimpBase = {
if (need.size()) {
if (mode == 'tog') {
- base.down('A').update(DimpCore.text.loading);
+ base.down('A').update(
+ new Element('SPAN')
+ .addClassName('imp-sidebar-mbox-loading')
+ .update('[' + DimpCore.text.loading + ']')
+ );
}
this._listMboxes({
all: Number(mode == 'expall'),
diff --git a/imp/lib/Ajax/Application/Handler/Common.php b/imp/lib/Ajax/Application/Handler/Common.php
index 63c548683af..64e46111a15 100644
--- a/imp/lib/Ajax/Application/Handler/Common.php
+++ b/imp/lib/Ajax/Application/Handler/Common.php
@@ -703,14 +703,16 @@ protected function _handleBadComposeAddr(IMP_Compose_Exception_Address $e)
? json_decode($this->vars->addr_ac, true)
: array();
- foreach ($e->addresses as $key => $val) {
- $notification->push(sprintf(_("Invalid e-mail address (%s)."), $key), 'horde.warning');
+ foreach ($e as $val) {
+ $addr = strval($val->address);
+ $notification->push($val->error, 'horde.warning');
+
foreach ($addr_ac as $val2) {
- if ($key == $val2['addr']) {
+ if ($addr == $val2['addr']) {
$this->_base->queue->compose_addr(
$val2['id'],
$val2['itemid'],
- 'impACListItemBad'
+ ($val->level == $e::BAD) ? 'impACListItemBad' : 'impACListItemWarn'
);
}
}
diff --git a/imp/lib/Api.php b/imp/lib/Api.php
index bf0adaac04b..0b8576e16f9 100644
--- a/imp/lib/Api.php
+++ b/imp/lib/Api.php
@@ -94,7 +94,8 @@ public function mailboxList()
{
$iterator = new IMP_Ftree_IteratorFilter_Nocontainers(
IMP_Ftree_IteratorFilter::create(
- IMP_Ftree_IteratorFilter::NO_REMOTE
+ IMP_Ftree_IteratorFilter::NO_REMOTE |
+ IMP_Ftree_IteratorFilter::UNSUB_PREF
)
);
$mboxes = array();
diff --git a/imp/lib/Basic/Listinfo.php b/imp/lib/Basic/Listinfo.php
new file mode 100644
index 00000000000..227b64bd0c2
--- /dev/null
+++ b/imp/lib/Basic/Listinfo.php
@@ -0,0 +1,136 @@
+
+ * @category Horde
+ * @copyright 2013 Horde LLC
+ * @license http://www.horde.org/licenses/gpl GPL
+ * @package IMP
+ */
+class IMP_Basic_Listinfo extends IMP_Basic_Base
+{
+ /**
+ */
+ protected function _init()
+ {
+ global $injector, $page_output;
+
+ $imp_mailbox = $this->indices->mailbox->list_ob;
+ list($m, $u) = $this->indices->getSingle();
+ $imp_indices = $imp_mailbox->getFullThread($u, $m);
+
+ if (!count($imp_indices)) {
+ throw new IMP_Exception(_("Could not load message."));
+ }
+
+ /* Parse the message. */
+ try {
+ $imp_contents = $injector->getInstance('IMP_Factory_Contents')->create(new IMP_Indices($imp_mailbox));
+ } catch (IMP_Exception $e) {
+ throw new IMP_Exception(_("Could not load message."));
+ }
+
+ $view = new Horde_View(array(
+ 'templatePath' => IMP_TEMPLATES . '/listinfo'
+ ));
+
+ $listheaders = $injector->getInstance('Horde_ListHeaders');
+ $mime_headers = $imp_contents->getHeader();
+
+ $view->headers = array();
+ foreach ($listheaders->headers() as $key => $val) {
+ if ($data = $mime_headers->getValue($key)) {
+ $view->headers[$val] = $this->_parseListHeaders($val, $data);
+ }
+ }
+
+ $this->output = $view->render('listinfo');
+ $this->title = _("Mailing List Information");
+
+ $page_output->topbar = $page_output->sidebar = false;
+ }
+
+ /**
+ */
+ public function status()
+ {
+ }
+
+ /**
+ * @param array $opts Options:
+ *
+ * - buid: (string) BUID of message.
+ * - full: (boolean) Full URL?
+ * - mailbox: (string) Mailbox of message.
+ *
+ */
+ static public function url(array $opts = array())
+ {
+ $url = Horde::url('basic.php')
+ ->add('page', 'listinfo')
+ ->unique()
+ ->setRaw(!empty($opts['full']));
+
+ if (!empty($opts['mailbox'])) {
+ $url->add(array(
+ 'buid' => $opts['buid'],
+ 'mailbox' => IMP_Mailbox::get($opts['mailbox'])->form_to
+ ));
+ }
+
+ return $url;
+ }
+
+ /**
+ * Parse the information in mailing list headers.
+ *
+ * @param string $id The header ID.
+ * @param string $data The header text to process.
+ *
+ * @return string The HTML-escaped header value.
+ */
+ protected function _parseListHeaders($id, $data)
+ {
+ global $injector;
+
+ $parser = $injector->getInstance('Horde_ListHeaders');
+ $text_filter = $injector->getInstance('Horde_Core_Factory_TextFilter');
+
+ foreach ($parser->parse($id, $data) as $val) {
+ /* RFC 2369 [2] states that we should only show the *FIRST* URL
+ * that appears in a header that we can adequately handle. */
+ if (stripos($val->url, 'mailto:') === 0) {
+ $url = substr($val->url, 7);
+ $clink = new IMP_Compose_Link($url);
+ $out = Horde::link($clink->link()) . $url . '';
+ foreach ($val->comments as $val2) {
+ $out .= htmlspecialchars('(' . $val2 . ')');
+ }
+ return $out;
+ } elseif ($url = $text_filter->filter($val->url, 'Linkurls')) {
+ $out = $url;
+ foreach ($val->comments as $val2) {
+ $out .= htmlspecialchars('(' . $val2 . ')');
+ }
+ return $out;
+ }
+ }
+
+ return htmlspecialchars($data);
+ }
+
+}
diff --git a/imp/lib/Basic/Message.php b/imp/lib/Basic/Message.php
index 8ab1d48e1e8..334fd8ff9da 100644
--- a/imp/lib/Basic/Message.php
+++ b/imp/lib/Basic/Message.php
@@ -332,21 +332,11 @@ protected function _init()
/* Determine if all/list/user-requested headers needed. */
$all_headers = $this->vars->show_all_headers;
- $list_headers = $this->vars->show_list_headers;
$user_hdrs = $imp_ui->getUserHeaders();
/* Check for the presence of mailing list information. */
$list_info = $imp_ui->getListInformation($mime_headers);
- /* See if the mailing list information has been requested to be
- * displayed. */
- if ($list_info['exists'] && ($list_headers || $all_headers)) {
- $all_list_headers = $this->_parseAllListHeaders($mime_headers);
- $list_headers_lookup = $mime_headers->listHeaders();
- } else {
- $all_list_headers = array();
- }
-
/* Display all headers or, optionally, the user-specified headers for
* the current identity. */
$full_headers = array();
@@ -357,7 +347,6 @@ protected function _init()
/* Skip the header if we have already dealt with it. */
if (!isset($display_headers[$lc_head]) &&
- !isset($all_list_headers[$lc_head]) &&
(!in_array($lc_head, array('importance', 'x-priority')) ||
!isset($display_headers['priority']))) {
$full_headers[$lc_head] = $val;
@@ -378,7 +367,7 @@ protected function _init()
* need other stuff in the query string, so we need to do an
* add/remove of uid info. */
$selfURL = $mailbox->url(Horde::selfUrlParams()->remove(array('actionID')), $buid)->add('message_token', $message_token);
- $headersURL = $selfURL->copy()->remove(array('show_all_headers', 'show_list_headers'));
+ $headersURL = $selfURL->copy()->remove(array('show_all_headers'));
/* Generate previous/next links. */
$prev_msg = $imp_mailbox[$imp_mailbox->getIndex() - 1];
@@ -730,7 +719,7 @@ protected function _init()
'title' => _("Headers"),
'nocheck' => true
));
- if ($all_headers || $list_headers) {
+ if ($all_headers) {
$a_view->common_headers = Horde::widget(array(
'url' => $headersURL,
'title' => _("Show Common Headers"),
@@ -744,9 +733,14 @@ protected function _init()
'nocheck' => true
));
}
- if ($list_info['exists'] && !$list_headers) {
+ if ($list_info['exists']) {
$a_view->list_headers = Horde::widget(array(
- 'url' => $headersURL->copy()->add('show_list_headers', 1),
+ 'onclick' => Horde::popupJs(IMP_Basic_Listinfo::url(array(
+ 'buid' => $buid,
+ 'mailbox' => $mailbox
+ )), array(
+ 'urlencode' => true
+ )),
'title' => _("Show Mailing List Information"),
'nocheck' => true
));
@@ -776,12 +770,6 @@ protected function _init()
);
}
}
- foreach ($all_list_headers as $head => $val) {
- $hdrs[] = array(
- 'name' => $list_headers_lookup[$head],
- 'val' => $val
- );
- }
/* Determine the fields that will appear in the MIME info entries. */
$part_info = $part_info_display = array('icon', 'description', 'size');
@@ -967,64 +955,4 @@ protected function _returnToMailbox($start = null, $actID = null)
$this->output = $ob->output;
}
- /**
- * Parses all of the available mailing list headers.
- *
- * @param Horde_Mime_Headers $headers A Horde_Mime_Headers object.
- *
- * @return array Keys are the list header names, values are the
- * parsed list header values.
- */
- protected function _parseAllListHeaders($headers)
- {
- $ret = array();
-
- foreach (array_keys($headers->listHeaders()) as $val) {
- if ($data = $headers->getValue($val)) {
- $ret[$val] = $this->_parseListHeaders($val, $data);
- }
- }
-
- return $ret;
- }
-
- /**
- * Parse the information in mailing list headers.
- *
- * @param string $id The header ID.
- * @param string $data The header text to process.
- *
- * @return string The header value.
- */
- protected function _parseListHeaders($id, $data)
- {
- $output = '';
- $parser = $GLOBALS['injector']->getInstance('Horde_ListHeaders');
- $text_filter = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter');
-
- foreach ($parser->parse($id, $data) as $val) {
- /* RFC 2369 [2] states that we should only show the *FIRST* URL
- * that appears in a header that we can adequately handle. */
- if (stripos($val->url, 'mailto:') === 0) {
- $url = substr($val->url, 7);
- $clink = new IMP_Compose_Link($url);
- $output = Horde::link($clink->link()) . $url . '';
- foreach ($val->comments as $val2) {
- $output .= ' (' . $val2 . ')';
- }
- break;
- } elseif ($url = $text_filter->filter($val->url, 'linkurls')) {
- $output = $url;
- foreach ($val->comments as $val2) {
- $output .= ' (' . $val2 . ')';
- }
- break;
- }
- }
-
- return strlen($output)
- ? $output
- : htmlspecialchars($data);
- }
-
}
diff --git a/imp/lib/Compose.php b/imp/lib/Compose.php
index 6a75676def3..d4f0182f3f0 100644
--- a/imp/lib/Compose.php
+++ b/imp/lib/Compose.php
@@ -1165,7 +1165,8 @@ protected function _prepSendMessageAssert(Horde_Mail_Rfc822_List $email,
protected function _prepSendMessageEncode(Horde_Mail_Rfc822_List $email,
$charset)
{
- $exception = null;
+ $exception = new IMP_Compose_Exception_Address();
+ $hook = true;
$out = array();
foreach ($email as $val) {
@@ -1183,16 +1184,48 @@ protected function _prepSendMessageEncode(Horde_Mail_Rfc822_List $email,
IMP::parseAddressList($tmp, array(
'validate' => true
));
- $out[] = $tmp;
+
+ $error = null;
+
+ if ($hook) {
+ try {
+ $error = Horde::callHook('compose_addr', array($tmp), 'imp');
+ } catch (Horde_Exception_HookNotSet $e) {
+ $hook = false;
+ }
+ }
} catch (Horde_Mail_Exception $e) {
- if (is_null($exception)) {
- $exception = new IMP_Compose_Exception_Address();
+ $error = array(
+ 'msg' => sprintf(_("Invalid e-mail address (%s)."), $val)
+ );
+ }
+
+ if (is_array($error)) {
+ switch (isset($error['level']) ? $error['level'] : $exception::BAD) {
+ case $exception::WARN:
+ case 'warn':
+ if (!empty($this->_metadata['warn_addr']) &&
+ in_array(strval($val), $this->_metadata['warn_addr'])) {
+ $out[] = $tmp;
+ continue 2;
+ }
+ $this->_metadata['warn_addr'][] = strval($val);
+ $this->changed = 'changed';
+ $level = $exception::WARN;
+ break;
+
+ default:
+ $level = $exception::BAD;
+ break;
}
- $exception->addresses[$val->writeAddress()] = $e;
+
+ $exception->addAddress($val, $error['msg'], $level);
+ } else {
+ $out[] = $tmp;
}
}
- if ($exception) {
+ if (count($exception)) {
throw $exception;
}
diff --git a/imp/lib/Compose/Exception/Address.php b/imp/lib/Compose/Exception/Address.php
index 1cde28727cb..593ec00993e 100644
--- a/imp/lib/Compose/Exception/Address.php
+++ b/imp/lib/Compose/Exception/Address.php
@@ -22,13 +22,61 @@
* @license http://www.horde.org/licenses/gpl GPL
* @package IMP
*/
-class IMP_Compose_Exception_Address extends IMP_Compose_Exception
+class IMP_Compose_Exception_Address
+extends IMP_Compose_Exception
+implements Countable, IteratorAggregate
{
+ /* Severity level. */
+ const BAD = 1;
+ const WARN = 2;
+
/**
- * The list of addresses (keys) and Horde_Mail_Exception objects (values).
+ * The list of error addresses.
*
* @var array
*/
- public $addresses = array();
+ protected $_addresses = array();
+
+ /**
+ * Add an address to the bad list.
+ *
+ * @param Horde_Mail_Rfc822_Object $address Bad address.
+ * @param Exception|string $msg Error message.
+ * @param integer $level Severity level.
+ */
+ public function addAddress(
+ Horde_Mail_Rfc822_Object $address, $msg, $level = self::BAD
+ )
+ {
+ $ob = new stdClass;
+ $ob->address = $address;
+ $ob->error = ($msg instanceof Exception)
+ ? $msg->getMessage()
+ : strval($msg);
+ $ob->level = $level;
+
+ $this->_addresses[] = $ob;
+ }
+
+ /* Countable method. */
+
+ /**
+ * Returns the number of error addresses.
+ *
+ * @return integer The number of error addresses.
+ */
+ public function count()
+ {
+ return count($this->_addresses);
+ }
+
+ /* IteratorAggregate method. */
+
+ /**
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_addresses);
+ }
}
diff --git a/imp/lib/Dynamic/Mailbox.php b/imp/lib/Dynamic/Mailbox.php
index 7de6866e281..ad4ff136062 100644
--- a/imp/lib/Dynamic/Mailbox.php
+++ b/imp/lib/Dynamic/Mailbox.php
@@ -134,6 +134,7 @@ protected function _addMailboxVars()
$this->js_conf += array_filter(array(
// URLs
+ 'URI_LISTINFO' => strval(IMP_Basic_Listinfo::url(array('full' => true))),
'URI_MESSAGE' => strval(IMP_Dynamic_Message::url()->setRaw(true)),
'URI_PORTAL' => strval($registry->getServiceLink('portal')->setRaw(true)),
'URI_PREFS_IMP' => strval($registry->getServiceLink('prefs', 'imp')->setRaw(true)),
@@ -428,7 +429,8 @@ protected function _addMailboxVars()
'save' => _("Save"),
'viewsource' => _("View Source"),
'allparts' => _("All Parts"),
- 'thread' => _("View Thread")
+ 'thread' => _("View Thread"),
+ 'listinfo' => _("List Info")
);
if (empty($conf['user']['allow_view_source'])) {
diff --git a/imp/lib/Dynamic/Message.php b/imp/lib/Dynamic/Message.php
index 83f72049d51..af084351ea4 100644
--- a/imp/lib/Dynamic/Message.php
+++ b/imp/lib/Dynamic/Message.php
@@ -75,6 +75,8 @@ protected function _init()
$ajax_queue = $injector->getInstance('IMP_Ajax_Queue');
$ajax_queue->poll($this->indices->mailbox);
+ list(,$buid) = $this->indices->buids->getSingle();
+
foreach (array('from', 'to', 'cc', 'bcc', 'replyTo', 'log') as $val) {
if (!empty($msg_res[$val])) {
$js_vars['DimpMessage.' . $val] = $msg_res[$val];
@@ -82,8 +84,16 @@ protected function _init()
}
if (!empty($msg_res['list_info']['exists'])) {
$js_vars['DimpMessage.reply_list'] = true;
+ $this->view->listinfo = Horde::popupJs(
+ IMP_Basic_Listinfo::url(array(
+ 'buid' => $buid,
+ 'mailbox' => $this->indices->mailbox
+ )), array(
+ 'urlencode' => true
+ )
+ );
}
- list(,$js_vars['DimpMessage.buid']) = $this->indices->buids->getSingle();
+ $js_vars['DimpMessage.buid'] = $buid;
$js_vars['DimpMessage.mbox'] = $this->indices->mailbox->form_to;
$js_vars['DimpMessage.tasks'] = $injector->getInstance('Horde_Core_Factory_Ajax')->create('imp', $this->vars)->getTasks();
diff --git a/imp/package.xml b/imp/package.xml
index dda8587a80c..9c3f34a3505 100644
--- a/imp/package.xml
+++ b/imp/package.xml
@@ -33,6 +33,8 @@
GPL-2.0
+* [mms] Better reporting of e-mail address errors when composing.
+* [mms] Add list information display to dynamic view.
* [jan] Display signature in compose view.
* [mms] Add keyboard shortcuts to allow a more granular scroll of the dynamic preview pane (Request #12750).
* [mms] Add country flag graphic to contact image information in dynamic view.
@@ -1283,7 +1285,7 @@
Horde_ListHeaders
pear.horde.org
- 1.0.0
+ 1.1.0
2.0.0alpha1
2.0.0alpha1
diff --git a/imp/templates/dynamic/message.html.php b/imp/templates/dynamic/message.html.php
index f7be35a9da2..41ac145e241 100644
--- a/imp/templates/dynamic/message.html.php
+++ b/imp/templates/dynamic/message.html.php
@@ -57,6 +57,14 @@
+
+listinfo): ?>
+