File Coverage

lib/Email/MIME/CreateHTML.pm
Criterion Covered Total %
statement 112 114 98.2
branch 41 46 89.1
condition 23 36 63.8
subroutine 15 17 88.2
pod 3 6 50.0
total 194 219 88.5


line stmt bran cond sub pod time code
1             ###############################################################################
2             # Purpose : Build HTML emails
3             # Author : Tony Hennessy
4             # Created : Aug 2006
5             ###############################################################################
6              
7             package Email::MIME::CreateHTML;
8              
9 2     2   111076 use strict;
  2         4  
  2         68  
10 2     2   7 use Carp;
  2         3  
  2         123  
11 2     2   8 use Exporter;
  2         4  
  2         52  
12 2     2   601 use Email::MIME;
  2         26344  
  2         39  
13 2     2   1026 use HTML::TokeParser::Simple;
  2         33059  
  2         59  
14 2     2   14 use HTML::Tagset;
  2         2  
  2         60  
15              
16             our $VERSION = '1.041';
17              
18 2     2   727 use Email::MIME::CreateHTML::Resolver;
  2         5  
  2         66  
19              
20             #Globals
21 2     2   10 use vars qw(%EMBED @EXPORT_OK @ISA);
  2         2  
  2         2115  
22             %EMBED = (
23             'bgsound' => {'src'=>1},
24             'body' => {'background'=>1},
25             'img' => {'src'=>1},
26             'input' => {'src'=>1},
27             'table' => {'background'=>1},
28             'td' => {'background'=>1},
29             'th' => {'background'=>1},
30             'tr' => {'background'=>1},
31             );
32             @EXPORT_OK = qw(embed_objects parts_for_objects build_html_email);
33             @ISA = qw(Exporter);
34              
35             #
36             # Public routines used by create_html and also exportable
37             #
38              
39             sub embed_objects {
40 9     9 1 12 my ($html, $args) = @_;
41 9 100 66     38 my $embed = ( defined $args->{embed} && $args->{embed} eq '0' ) ? 0 : 1;
42 9 100 66     33 my $inline_css = ( defined $args->{inline_css} && $args->{inline_css} eq '0' ) ? 0 : 1;
43 9         53 my $resolver = new Email::MIME::CreateHTML::Resolver($args);
44 8   50     32 my $embed_tags = $args->{'embed_elements'} || \%EMBED;
45            
46 8 50 66     25 return ($html, {}) unless ( $embed || $inline_css ); #No-op unless one of these is set
47              
48 8         7 my ($html_modified, %embedded_cids);
49 8         56 my $parser = HTML::TokeParser::Simple->new( \$html );
50 8         1039 my $regex = '^(' . join('|',keys %HTML::Tagset::linkElements) . ')';
51 8         223 $regex = qr/$regex/;
52 8         34 while ( my $token = $parser->get_token ) {
53              
54 172 100       4022 unless ( $token->is_start_tag( $regex ) ) {
55 147         709 $html_modified .= $token->as_is;
56 147         678 next;
57             }
58 25         281 my $token_tag = $token->get_tag();
59 25         117 my $token_attrs = $token->get_attr();
60              
61             # inline_css
62 25 100 66     244 if ( $token_tag eq 'link' && $token_attrs->{type} eq 'text/css' ) {
63 2 100       6 unless ( $inline_css ) {
64 1         4 $html_modified .= $token->as_is;
65 1         5 next;
66             }
67 1         2 my $link = $token_attrs->{'href'};
68 1         4 my ($content,$filename,$mimetype,$encoding) = $resolver->get_resource( $link );
69 1         4 $html_modified .= "\n".'\n";
72 1         4 next;
73             }
74              
75             # rewrite and embed
76 23         22 for my $attr ( @{ $HTML::Tagset::linkElements{$token_tag} } ) {
  23         49  
77 53 100       433 if ( defined $token_attrs->{$attr} ) {
78 11         16 my $link = $token_attrs->{$attr};
79 11 100       32 next if ($link =~ m/^cid:/i);
80              
81             # embed
82 5 100 66     28 if ( $embed && $embed_tags->{$token_tag}->{$attr} ) {
83 4 100       10 unless ( defined $embedded_cids{$link} ) {
84             # make a unique cid
85 3         14 my $newcid = time().$$.int(rand(1e6));
86 3         5 $embedded_cids{$link} = $newcid;
87             }
88 4         62 my $link_rewrite = "cid:".$embedded_cids{$link};
89 4         13 $token->set_attr( $attr => $link_rewrite );
90             }
91             }
92             }
93 23         48 $html_modified .= $token->as_is;
94             }
95              
96 8         121 my %objects = reverse %embedded_cids; #invert mapping
97 8         86 return ($html_modified, \%objects);
98             }
99              
100             sub parts_for_objects {
101 8     8 1 8 my ($objects, $args) = @_;
102 8         27 my $resolver = new Email::MIME::CreateHTML::Resolver($args);
103              
104 8         7 my @html_mime_parts;
105 8         17 foreach my $cid (keys %$objects) {
106 9 50       52 croak "Content-Id '$cid' contains bad characters" unless ($cid =~ m/^[\w\-\@\.]+$/);
107 9 50       19 croak "Content-Id must be given" unless length($cid);
108            
109 9         11 my $path = $objects->{$cid};
110 9         23 my ($content,$filename,$mimetype,$encoding) = $resolver->get_resource( $path );
111            
112 9   50     28 $mimetype ||= 'application/octet-stream';
113 9         71 my $newpart = Email::MIME->create(
114             attributes => {
115             content_type => $mimetype,
116             encoding => $encoding,
117             disposition => 'inline', # maybe useful rfc2387
118             charset => undef,
119             name => $filename,
120             },
121             body => $content,
122             );
123 9         8013 $newpart->header_set('Content-ID',"<$cid>");
124             # $newpart->header_set("Content-Transfer-Encoding", "base64");
125 9         289 push @html_mime_parts , $newpart;
126             }
127 8         31 return @html_mime_parts;
128             }
129              
130             sub build_html_email {
131 8     8 1 13 my($header, $html, $body_attributes, $html_mime_parts, $plain_text_mime) = @_;
132              
133 8 100       24 $body_attributes->{charset} = 'UTF-8' unless exists $body_attributes->{charset};
134 8 100       21 $body_attributes->{encoding}= 'quoted-printable' unless exists $body_attributes->{encoding};
135              
136 8         12 my $email;
137 8 100 100     72 if ( ! scalar(@$html_mime_parts) && ! defined($plain_text_mime) ) {
    100 66        
    100 66        
    50 33        
138             # HTML, no embedded objects, no text alternative
139 2         13 $email = Email::MIME->create(
140             header => $header,
141             attributes => $body_attributes,
142             body_str => $html,
143             );
144             }
145             elsif ( ! scalar(@$html_mime_parts) && defined($plain_text_mime) ) {
146             # HTML, no embedded objects, with text alternative
147 1         6 $email = Email::MIME->create(
148             header => $header,
149             attributes => {content_type=>'multipart/alternative'},
150             parts => [
151             $plain_text_mime,
152             Email::MIME->create(
153             attributes => $body_attributes,
154             body_str => $html,
155             ),
156             ],
157             );
158             }
159             elsif ( scalar(@$html_mime_parts) && ! defined($plain_text_mime) ) {
160             # HTML with embedded objects, no text alternative
161 4         19 $email = Email::MIME->create(
162             header => $header,
163             attributes => {content_type=>'multipart/related'},
164             parts => [
165             Email::MIME->create(
166             attributes => $body_attributes,
167             body_str => $html,
168             ),
169             @$html_mime_parts,
170             ],
171             );
172             }
173             elsif ( scalar(@$html_mime_parts) && defined($plain_text_mime) ) {
174             # HTML with embedded objects, with text alternative
175 1         7 $email = Email::MIME->create(
176             header => $header,
177             attributes => {content_type=>'multipart/alternative'},
178             parts => [
179             $plain_text_mime,
180             Email::MIME->create(
181             attributes => {content_type=>'multipart/related'},
182             parts => [
183             Email::MIME->create(
184             attributes => $body_attributes,
185             body_str => $html,
186             ),
187             @$html_mime_parts,
188             ],
189             ),
190             ],
191             );
192             }
193 8         25268 return $email;
194             }
195              
196             # Add to Email::MIME
197             package # Hide from PAUSE
198             Email::MIME;
199              
200 2     2   10 use strict;
  2         3  
  2         70  
201 2     2   8 use Carp;
  2         2  
  2         88  
202 2     2   8 use Email::MIME::Creator;
  2         3  
  2         420  
203              
204             sub create_html {
205 9     9 0 32796 my ($class, %args) = @_;
206              
207             #Argument checking/defaulting
208 9   33     33 my $html = $args{body} || croak "You must supply a body";
209 9   100     33 my $objects = $args{'objects'} || undef;
210            
211             # Make plain text Email::MIME object, we will never use this alone so we don't need the headers
212 9         8 my $plain_text_mime;
213 9 100       28 if ( exists($args{text_body}) ) {
214 2 50       3 my %text_body_attributes = ( (content_type=>'text/plain'), %{$args{text_body_attributes} || {}} );
  2         13  
215 2         10 $plain_text_mime = $class->create(
216             attributes => \%text_body_attributes,
217             body => $args{text_body},
218             );
219             }
220              
221             # Parse the HTML and create a CID mapping for objects to embed
222 9         939 my $embedded_cids;
223 9         25 ($html, $embedded_cids) = Email::MIME::CreateHTML::embed_objects($html, \%args);
224              
225             # Create parts for each embedded object
226 8         13 my @html_mime_parts;
227 8 100       26 push @html_mime_parts, Email::MIME::CreateHTML::parts_for_objects($objects, \%args) if ($objects);
228 8 100       23 push @html_mime_parts, Email::MIME::CreateHTML::parts_for_objects($embedded_cids, \%args) if(%$embedded_cids);
229              
230             # Create the mail
231 8         14 my $header = $args{header};
232 8 100       10 my %body_attributes = ( (content_type=>'text/html'), %{$args{body_attributes} || {}});
  8         50  
233 8         27 my $email = Email::MIME::CreateHTML::build_html_email($header, $html, \%body_attributes, \@html_mime_parts, $plain_text_mime);
234 8         76 return $email;
235             }
236              
237             #Log::Trace stubs
238 0     0 0   sub DUMP {}
239 0     0 0   sub TRACE {}
240              
241             1;
242              
243             __END__