Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Lighthouse 1444 patch #471

Closed
wants to merge 5 commits into from

6 participants

@tazmaniax

Added support for embedded images to Mailer

@mbknor
Collaborator

Can you please add testcase to just-test-cases?

@tazmaniax
@pepite
Owner

Thanks for your work, can you update the documentation as well?

@MarcoStruck

Hello. I would like to use this patch in play 1. What do I have to do?
Write tests and docs? But where? Thanks for a hint.
Regards, Marco

@tazmaniax

Hi Marco, I've been meaning to update documentation / tests for a while but been struggling with time and distractions. I've just made some changes to the manual documentation but not sure what other relevant documentation should be updated as well. Unfortunately that's all I have time for at the moment. Happy to answer any questions.

@Notalifeform
Collaborator

@MarcoStruck you could extend just-test-cases/test/mailer.test.html to prove it all works.
Let me know if you need any help.

regards,

Robert

@xael-fry
Collaborator

I added some tests, and some content in the documentation (cf #605)

@xael-fry
Collaborator

Can be closed since we merge it with the pull request #605

@Notalifeform
Collaborator

indeed. closed. thx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 24, 2012
  1. @tazmaniax
Commits on Apr 9, 2013
  1. @tazmaniax

    Update emails.textile

    tazmaniax authored
  2. @tazmaniax

    Update emails.textile

    tazmaniax authored
    Fixed formatting
  3. @tazmaniax

    Update emails.textile

    tazmaniax authored
  4. @tazmaniax

    Update emails.textile

    tazmaniax authored
    Fixed example
This page is out of date. Refresh to see the latest.
View
8 documentation/manual/emails.textile
@@ -118,6 +118,14 @@ For example, to send an e-mail from a Job running on the playframework.org web s
bc. application.baseUrl=http://www.playframework.org/
+h3. Links to embedded images
+
+You can add links to embedded images by using the embeddedImage tag like this:
+
+bc. #{embeddedImage src:'public/images/image1.jpg' name:'myImage' /}
+
+This will attach the image to the email and create an internal reference inside the the html/text content.
+
h2. <a name="smtp">SMTP configuration</a>
E-mail functionality is configured by several "mail configuration":configuration#mail properties:
View
170 framework/src/play/mvc/Mailer.java
@@ -1,5 +1,10 @@
package play.mvc;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -8,8 +13,11 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.mail.*;
+
import play.Logger;
import play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer;
import play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesSupport;
@@ -17,9 +25,13 @@
import play.exceptions.TemplateNotFoundException;
import play.exceptions.UnexpectedException;
import play.libs.Mail;
+import play.libs.MimeTypes;
import play.templates.Template;
import play.templates.TemplateLoader;
+import play.vfs.VirtualFile;
+import javax.activation.DataSource;
+import javax.activation.URLDataSource;
import javax.mail.internet.InternetAddress;
/**
@@ -120,6 +132,149 @@ public static void setFrom(Object from) {
map.put("from", from);
infos.set(map);
}
+
+ private static class InlineImage {
+ /** content id */
+ private final String cid;
+ /** <code>DataSource</code> for the content */
+ private final DataSource dataSource;
+
+ public InlineImage(String cid, DataSource dataSource) {
+ super();
+ this.cid = cid;
+ this.dataSource = dataSource;
+ }
+
+ public String getCid() {
+ return this.cid;
+ }
+
+ public DataSource getDataSource() {
+ return this.dataSource;
+ }
+ }
+
+ private static class VirtualFileDataSource implements DataSource {
+ private final VirtualFile virtualFile;
+
+ public VirtualFileDataSource(VirtualFile virtualFile) {
+ this.virtualFile = virtualFile;
+ }
+
+ public VirtualFileDataSource(String relativePath) {
+ this.virtualFile = VirtualFile.fromRelativePath(relativePath);
+ }
+
+ @Override
+ public String getContentType() {
+ return MimeTypes.getContentType(this.virtualFile.getName());
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return this.virtualFile.inputstream();
+ }
+
+ @Override
+ public String getName() {
+ return this.virtualFile.getName();
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return this.virtualFile.outputstream();
+ }
+
+ public VirtualFile getVirtualFile() {
+ return this.virtualFile;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof VirtualFileDataSource)) {
+ return false;
+ }
+
+ VirtualFileDataSource rhs = (VirtualFileDataSource)obj;
+
+ return this.virtualFile.equals(rhs.virtualFile);
+ }
+ }
+
+ public static String getEmbedddedSrc(String urlString, String name) {
+ HashMap<String, Object> map = infos.get();
+ if (map == null) {
+ throw new UnexpectedException("Mailer not instrumented ?");
+ }
+
+ URL url = null;
+ try {
+ url = new URL(urlString);
+ } catch (MalformedURLException e1) {
+ throw new UnexpectedException("Invalid URL '" + urlString + "'", e1);
+ }
+
+ if (StringUtils.isEmpty(name)) {
+ String[] parts = url.getPath().split("/");
+ name = parts[parts.length-1];
+ }
+
+ if (StringUtils.isEmpty(name)) {
+ throw new UnexpectedException("name cannot be null or empty");
+ }
+
+ DataSource dataSource = url.getProtocol().equals("file")
+ ? new VirtualFileDataSource(url.getFile())
+ : new URLDataSource(url);
+
+ Map<String, InlineImage> inlineEmbeds = (Map<String, InlineImage>)map.get("inlineEmbeds");
+
+ // Check if a URLDataSource for this name has already been attached;
+ // if so, return the cached CID value.
+ if (inlineEmbeds != null && inlineEmbeds.containsKey(name)) {
+ InlineImage ii = inlineEmbeds.get(name);
+
+ if (ii.getDataSource() instanceof URLDataSource) {
+ URLDataSource urlDataSource = (URLDataSource)ii.getDataSource();
+ // Make sure the supplied URL points to the same thing
+ // as the one already associated with this name.
+ // NOTE: Comparing URLs with URL.equals() is a blocking operation
+ // in the case of a network failure therefore we use
+ // url.toExternalForm().equals() here.
+ if (!url.toExternalForm().equals(urlDataSource.getURL().toExternalForm())) {
+ throw new UnexpectedException("embedded name '" + name + "' is already bound to URL "
+ + urlDataSource.getURL() + "; existing names cannot be rebound");
+ }
+ } else if (!ii.getDataSource().equals(dataSource)) {
+ throw new UnexpectedException("embedded name '" + name + "' is already bound to URL "
+ + dataSource.getName() + "; existing names cannot be rebound");
+ }
+
+ return "cid:" + ii.getCid();
+ }
+
+ // Verify that the data source is valid.
+ InputStream is = null;
+ try {
+ is = dataSource.getInputStream();
+ } catch (IOException e) {
+ throw new UnexpectedException("Invalid URL", e);
+ } finally {
+ IOUtils.closeQuietly(is);
+ }
+
+ String cid = RandomStringUtils.randomAlphabetic(HtmlEmail.CID_LENGTH).toLowerCase();
+ InlineImage ii = new InlineImage(cid, dataSource);
+
+ if (inlineEmbeds == null) {
+ inlineEmbeds = new HashMap<String, InlineImage>();
+ map.put("inlineEmbeds", inlineEmbeds);
+ }
+ inlineEmbeds.put(name, ii);
+ infos.set(map);
+
+ return "cid:" + cid;
+ }
/**
* Can be of the form xxx <m@m.com>
@@ -245,7 +400,7 @@ public static void addHeader(String key, String value) {
final Object replyTo = infos.get().get("replyTo");
Email email = null;
- if (infos.get().get("attachments") == null) {
+ if (infos.get().get("attachments") == null && infos.get().get("inlineEmbeds") == null) {
if (StringUtils.isEmpty(bodyHtml)) {
email = new SimpleEmail();
email.setMsg(bodyText);
@@ -269,11 +424,20 @@ public static void addHeader(String key, String value) {
htmlEmail.setTextMsg(bodyText);
}
email = htmlEmail;
+
+ Map<String, InlineImage> inlineEmbeds = (Map<String, InlineImage>) infos.get().get("inlineEmbeds");
+ if (inlineEmbeds != null) {
+ for (Map.Entry<String, InlineImage> entry : inlineEmbeds.entrySet()) {
+ htmlEmail.embed(entry.getValue().getDataSource(), entry.getKey(), entry.getValue().getCid());
+ }
+ }
}
MultiPartEmail multiPartEmail = (MultiPartEmail) email;
List<EmailAttachment> objectList = (List<EmailAttachment>) infos.get().get("attachments");
- for (EmailAttachment object : objectList) {
- multiPartEmail.attach(object);
+ if (objectList != null) {
+ for (EmailAttachment object : objectList) {
+ multiPartEmail.attach(object);
+ }
}
}
email.setCharset("utf-8");
View
30 framework/src/play/templates/FastTags.java
@@ -23,6 +23,7 @@
import play.exceptions.TemplateNotFoundException;
import play.libs.Codec;
import play.mvc.Http;
+import play.mvc.Mailer;
import play.mvc.Router.ActionDefinition;
import play.mvc.Scope.Flash;
import play.mvc.Scope.Session;
@@ -387,6 +388,35 @@ public static void _render(Map<?, ?> args, Closure body, PrintWriter out, Execut
throw new TemplateNotFoundException(e.getPath(), template.template, fromLine);
}
}
+
+ public static void _embeddedImage(Map<?, ?> args, Closure body, PrintWriter out, ExecutableTemplate template, int fromLine) {
+ Map<String, String> renderArgs = new HashMap<String, String>();
+ // Copy all arguments to render arguments, except for 'src' & 'name' arguments.
+ for (final Object attribute : args.keySet()) {
+ if (!"src".equalsIgnoreCase(attribute.toString()) && !"name".equalsIgnoreCase(attribute.toString()) && (args.get(attribute) != null)) {
+ renderArgs.put(attribute.toString(), args.get(attribute).toString());
+ }
+ }
+
+ out.print("<img");
+
+ Object src = args.get("src");
+ if (src != null) {
+ Object name = args.get("name");
+ renderArgs.put("src", Mailer.getEmbedddedSrc(src.toString(), (name != null) ? name.toString() : null));
+ }
+
+ for (final Object attrKey : renderArgs.keySet()) {
+ if (renderArgs.get(attrKey) != null) {
+ String value = renderArgs.get(attrKey);
+ if (value != null) {
+ out.print(" " + attrKey.toString() + "=\"" + value + "\"");
+ }
+ }
+ }
+
+ out.println("/>");
+ }
public static String serialize(Map<?, ?> args, String... unless) {
StringBuilder attrs = new StringBuilder();
Something went wrong with that request. Please try again.