From 03149131f754dd122f8707fbfc9e7ff47e9d6524 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sat, 10 Nov 2012 21:08:14 +0100 Subject: [PATCH] New feature: display attached images as thumbnails below message body --- config/main.inc.php.dist | 5 +++ program/include/html.php | 4 +- program/include/rcube_image.php | 23 +++++++--- program/steps/mail/func.inc | 49 ++++++++++++++++++--- program/steps/mail/get.inc | 36 ++++++++++++++++ skins/classic/mail.css | 59 ++++++++++++++++++++++++++ skins/larry/mail.css | 43 ++++++++++++++++++- skins/larry/templates/messagepart.html | 2 +- 8 files changed, 204 insertions(+), 17 deletions(-) diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 35fe0e8f6..dc76f1887 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -373,6 +373,11 @@ $rcmail_config['im_identify_path'] = null; // path to imagemagick convert binary $rcmail_config['im_convert_path'] = null; +// Size of thumbnails from image attachments displayed below the message content. +// Note: whether images are displayed at all depends on the 'inline_images' option. +// Set to 0 to display images in full size. +$rcmail_config['image_thumbnail_size'] = 240; + // maximum size of uploaded contact photos in pixel $rcmail_config['contact_photo_size'] = 160; diff --git a/program/include/html.php b/program/include/html.php index 880873ddc..0f93e969d 100644 --- a/program/include/html.php +++ b/program/include/html.php @@ -252,9 +252,9 @@ class html * @return string HTML code * @see html::tag() */ - public static function br() + public static function br($attrib = array()) { - return self::tag('br'); + return self::tag('br', $attrib); } /** diff --git a/program/include/rcube_image.php b/program/include/rcube_image.php index 80e8bd4f3..c0d4e878d 100644 --- a/program/include/rcube_image.php +++ b/program/include/rcube_image.php @@ -78,10 +78,11 @@ class rcube_image * * @param int $size Max width/height size * @param string $filename Output filename + * @param boolean $browser_compat Convert to image type displayable by any browser * - * @return bool True on success, False on failure + * @return mixed Output type on success, False on failure */ - public function resize($size, $filename = null) + public function resize($size, $filename = null, $browser_compat = false) { $result = false; $rcube = rcube::get_instance(); @@ -104,15 +105,22 @@ class rcube_image } $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps")); + $p['intype'] = $type; + + // convert to an image format every browser can display + if ($browser_compat && !in_array($type, array('jpg','gif','png'))) { + $type = 'jpg'; + } + $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75); - $p['-opts'] = array('-resize' => $size.'>'); + $p['-opts'] = array('-resize' => $p['size'].'>'); if (in_array($type, explode(',', $p['types']))) { // Valid type? - $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p); + $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {intype}:{in} {type}:{out}', $p); } if ($result === '') { - return true; + return $type; } } @@ -148,16 +156,19 @@ class rcube_image if ($props['gd_type'] == IMAGETYPE_JPEG) { $result = imagejpeg($image, $filename, 75); + $type = 'jpg'; } elseif($props['gd_type'] == IMAGETYPE_GIF) { $result = imagegif($image, $filename); + $type = 'gid'; } elseif($props['gd_type'] == IMAGETYPE_PNG) { $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS); + $type = 'png'; } if ($result) { - return true; + return $type; } } diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index f128a3834..c0d36daf4 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1186,7 +1186,9 @@ function rcmail_message_body($attrib) } // list images after mail body - if ($CONFIG['inline_images'] && !empty($MESSAGE->attachments)) { + if ($RCMAIL->config->get('inline_images', true) && !empty($MESSAGE->attachments)) { + $thumbnail_size = $RCMAIL->config->get('image_thumbnail_size', 240); + foreach ($MESSAGE->attachments as $attach_prop) { // skip inline images if ($attach_prop->content_id && $attach_prop->disposition == 'inline') { @@ -1195,12 +1197,45 @@ function rcmail_message_body($attrib) // Content-Type: image/*... if (rcmail_part_image_type($attach_prop)) { - $out .= html::tag('hr') . html::p(array('align' => "center"), - html::img(array( - 'src' => $MESSAGE->get_part_url($attach_prop->mime_id, true), - 'title' => $attach_prop->filename, - 'alt' => $attach_prop->filename, - ))); + // display thumbnails + if ($thumbnail_size) { + $show_link = array( + 'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false), + 'onclick' => sprintf( + 'return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)', + JS_OBJECT_NAME, + $attach_prop->mime_id, + rcmail_fix_mimetype($attach_prop->mimetype)) + ); + $out .= html::p('image-attachment', + html::a($show_link + array('class' => 'image-link'), + html::img(array( + 'class' => 'image-thumbnail', + 'src' => $MESSAGE->get_part_url($attach_prop->mime_id, true) . '&_thumb=1', + 'title' => $attach_prop->filename, + 'alt' => $attach_prop->filename, + 'style' => sprintf('max-width:%dpx; max-height:%dpx', $thumbnail_size, $thumbnail_size), + )) + ) . + html::span('image-filename', Q($attach_prop->filename)) . + html::span('image-filesize', Q($RCMAIL->show_bytes($attach_prop->size))) . + html::span('attachment-links', + html::a($show_link['href'] . '&_download=1', rcube_label('download')) + ) . + html::br(array('style' => 'clear:both')) + ); + } + else { + $out .= html::tag('fieldset', 'image-attachment', + html::tag('legend', 'image-filename', Q($attach_prop->filename)) . + html::p(array('align' => "center"), + html::img(array( + 'src' => $MESSAGE->get_part_url($attach_prop->mime_id, true), + 'title' => $attach_prop->filename, + 'alt' => $attach_prop->filename, + ))) + ); + } } } } diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index bcd57dee0..2397358a1 100644 --- a/program/steps/mail/get.inc +++ b/program/steps/mail/get.inc @@ -60,6 +60,42 @@ if (!empty($_GET['_frame'])) { exit; } +// render thumbnail of an image attachment +else if ($_GET['_thumb']) { + $pid = get_input_value('_part', RCUBE_INPUT_GET); + if ($part = $MESSAGE->mime_parts[$pid]) { + $thumbnail_size = $RCMAIL->config->get('image_thumbnail_size', 240); + $temp_dir = $RCMAIL->config->get('temp_dir'); + list(,$ext) = explode('/', $part->mimetype); + $cache_basename = $temp_dir . '/' . md5($MESSAGE->headers->messageID . $part->mime_id . ':' . $RCMAIL->user->ID . ':' . $thumbnail_size); + $cache_file = $cache_basename . '.' . $ext; + $mimetype = $part->mimetype; + + // render thumbnail image if not done yet + if (!is_file($cache_file)) { + $fp = fopen(($orig_name = $cache_basename . '.orig.' . $ext), 'w'); + $MESSAGE->get_part_content($part->mime_id, $fp); + fclose($fp); + + $image = new rcube_image($orig_name); + if ($imgtype = $image->resize($RCMAIL->config->get('image_thumbnail_size', 240), $cache_file, true)) { + $mimetype = 'image/' . $imgtype; + unlink($orig_name); + } + else { + rename($orig_name, $cache_file); + } + } + + if (is_file($cache_file)) { + header('Content-Type: ' . $mimetype); + readfile($cache_file); + } + } + + exit; +} + else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { if ($part = $MESSAGE->mime_parts[$pid]) { diff --git a/skins/classic/mail.css b/skins/classic/mail.css index 7408d49f1..85c53d569 100644 --- a/skins/classic/mail.css +++ b/skins/classic/mail.css @@ -1254,6 +1254,65 @@ div.message-htmlpart div.rcmBody color: #333333; } +#messagebody fieldset.image-attachment { + border: 0; + border-top: 1px solid #ccc; + margin: 1em 1em 0 1em; +} + +#messagebody fieldset.image-attachment p > img +{ + max-width: 80%; +} + +#messagebody legend.image-filename +{ + color: #999; + font-size: 0.9em; +} + +#messagebody p.image-attachment +{ + margin: 0 1em; + padding: 1em; + border-top: 1px solid #ccc; +} + +#messagebody p.image-attachment a.image-link +{ + float: left; + margin-right: 2em; + min-width: 160px; + min-height: 60px; + text-align: center; +} + +#messagebody p.image-attachment .image-filename +{ + display: block; + font-weight: bold; + line-height: 1.6em; +} + +#messagebody p.image-attachment .image-filesize +{ + font-size: 11px; + padding-right: 1em; +} + +#messagebody p.image-attachment .attachment-links a +{ + margin-right: 0.6em; + color: #cc0000; + font-size: 11px; + text-decoration: none; +} + +#messagebody p.image-attachment .attachment-links a:hover +{ + text-decoration: underline; +} + #openextwinlink { position: absolute; diff --git a/skins/larry/mail.css b/skins/larry/mail.css index eb623222a..48560abe2 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -1050,10 +1050,51 @@ div.message-part blockquote blockquote blockquote { border-bottom: 2px solid #f0f0f0; } -#messagebody > p > img { +#messagebody fieldset.image-attachment { + border: 0; + border-top: 1px solid #ccc; + margin-top: 1em; +} + +#messagebody fieldset.image-attachment p > img { max-width: 80%; } +#messagebody legend.image-filename { + color: #999; + font-size: 0.9em; + margin: 0 1em; +} + +#messagebody p.image-attachment { + position: relative; + padding: 1em; + border-top: 1px solid #ccc; +} + +#messagebody p.image-attachment a.image-link { + float: left; + display: block; + margin-right: 2em; + min-width: 160px; + min-height: 60px; + text-align: center; +} + +#messagebody p.image-attachment .image-filename { + display: block; + font-weight: bold; + line-height: 1.6em; +} + +#messagebody p.image-attachment .image-filesize { + padding-right: 1em; +} + +#messagebody p.image-attachment .attachment-links a { + margin-right: 0.6em; +} + #messagepartcontainer { position: absolute; top: 60px; diff --git a/skins/larry/templates/messagepart.html b/skins/larry/templates/messagepart.html index db078296f..e029973a8 100644 --- a/skins/larry/templates/messagepart.html +++ b/skins/larry/templates/messagepart.html @@ -4,7 +4,7 @@ <roundcube:object name="pagetitle" /> - +