File Coverage

lib/Email/MIME/CreateHTML.pm
Criterion Covered Total %
statement 110 112 98.2
branch 37 42 88.1
condition 23 36 63.8
subroutine 15 17 88.2
pod 3 6 50.0
total 188 213 88.2


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 1     1   169770 use strict;
  1         3  
  1         43  
10 1     1   5 use Carp;
  1         2  
  1         83  
11 1     1   6 use Exporter;
  1         3  
  1         35  
12 1     1   6 use Email::MIME;
  1         2  
  1         31  
13 1     1   1182 use HTML::TokeParser::Simple;
  1         30319  
  1         36  
14 1     1   10 use HTML::Tagset;
  1         3  
  1         63  
15              
16             our $VERSION = '1.040';
17              
18 1     1   406 use Email::MIME::CreateHTML::Resolver;
  1         3  
  1         40  
19              
20             #Globals
21 1     1   7 use vars qw(%EMBED @EXPORT_OK @ISA);
  1         2  
  1         1875  
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 8     8 1 21 my ($html, $args) = @_;
41 8 100 66     47 my $embed = ( defined $args->{embed} && $args->{embed} eq '0' ) ? 0 : 1;
42 8 100 66     43 my $inline_css = ( defined $args->{inline_css} && $args->{inline_css} eq '0' ) ? 0 : 1;
43 8         112 my $resolver = new Email::MIME::CreateHTML::Resolver($args);
44 7   50     74 my $embed_tags = $args->{'embed_elements'} || \%EMBED;
45            
46 7 50 66     35 return ($html, {}) unless ( $embed || $inline_css ); #No-op unless one of these is set
47              
48 7         10 my ($html_modified, %embedded_cids);
49 7         79 my $parser = HTML::TokeParser::Simple->new( \$html );
50 7         1331 my $regex = '^(' . join('|',keys %HTML::Tagset::linkElements) . ')';
51 7         156 $regex = qr/$regex/;
52 7         41 while ( my $token = $parser->get_token ) {
53              
54 147 100       5264 unless ( $token->is_start_tag( $regex ) ) {
55 124         893 $html_modified .= $token->as_is;
56 124         837 next;
57             }
58 23         380 my $token_tag = $token->get_tag();
59 23         179 my $token_attrs = $token->get_attr();
60              
61             # inline_css
62 23 100 66     304 if ( $token_tag eq 'link' && $token_attrs->{type} eq 'text/css' ) {
63 2 100       7 unless ( $inline_css ) {
64 1         3 $html_modified .= $token->as_is;
65 1         8 next;
66             }
67 1         3 my $link = $token_attrs->{'href'};
68 1         6 my ($content,$filename,$mimetype,$encoding) = $resolver->get_resource( $link );
69 1         4 $html_modified .= "\n".'\n";
72 1         5 next;
73             }
74              
75             # rewrite and embed
76 21         28 for my $attr ( @{ $HTML::Tagset::linkElements{$token_tag} } ) {
  21         61  
77 51 100       601 if ( defined $token_attrs->{$attr} ) {
78 11         21 my $link = $token_attrs->{$attr};
79 11 100       44 next if ($link =~ m/^cid:/i);
80              
81             # embed
82 5 100 66     40 if ( $embed && $embed_tags->{$token_tag}->{$attr} ) {
83 4 100       15 unless ( defined $embedded_cids{$link} ) {
84             # make a unique cid
85 3         21 my $newcid = time().$$.int(rand(1e6));
86 3         10 $embedded_cids{$link} = $newcid;
87             }
88 4         12 my $link_rewrite = "cid:".$embedded_cids{$link};
89 4         19 $token->set_attr( $attr => $link_rewrite );
90             }
91             }
92             }
93 21         65 $html_modified .= $token->as_is;
94             }
95              
96 7         117 my %objects = reverse %embedded_cids; #invert mapping
97 7         101 return ($html_modified, \%objects);
98             }
99              
100             sub parts_for_objects {
101 8     8 1 14 my ($objects, $args) = @_;
102 8         45 my $resolver = new Email::MIME::CreateHTML::Resolver($args);
103              
104 8         14 my @html_mime_parts;
105 8         28 foreach my $cid (keys %$objects) {
106 9 50       59 croak "Content-Id '$cid' contains bad characters" unless ($cid =~ m/^[\w\-\@\.]+$/);
107 9 50       30 croak "Content-Id must be given" unless length($cid);
108            
109 9         18 my $path = $objects->{$cid};
110 9         33 my ($content,$filename,$mimetype,$encoding) = $resolver->get_resource( $path );
111            
112 9   50     33 $mimetype ||= 'application/octet-stream';
113 9         106 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         10751 $newpart->header_set('Content-ID',"<$cid>");
124             # $newpart->header_set("Content-Transfer-Encoding", "base64");
125 9         336 push @html_mime_parts , $newpart;
126             }
127 8         42 return @html_mime_parts;
128             }
129              
130             sub build_html_email {
131 7     7 1 17 my($header, $html, $body_attributes, $html_mime_parts, $plain_text_mime) = @_;
132            
133 7         8 my $email;
134 7 100 100     91 if ( ! scalar(@$html_mime_parts) && ! defined($plain_text_mime) ) {
    100 66        
    100 66        
    50 33        
135             # HTML, no embedded objects, no text alternative
136 1         10 $email = Email::MIME->create(
137             header => $header,
138             attributes => $body_attributes,
139             body => $html,
140             );
141             }
142             elsif ( ! scalar(@$html_mime_parts) && defined($plain_text_mime) ) {
143             # HTML, no embedded objects, with text alternative
144 1         8 $email = Email::MIME->create(
145             header => $header,
146             attributes => {content_type=>'multipart/alternative'},
147             parts => [
148             $plain_text_mime,
149             Email::MIME->create(
150             attributes => $body_attributes,
151             body => $html,
152             ),
153             ],
154             );
155             }
156             elsif ( scalar(@$html_mime_parts) && ! defined($plain_text_mime) ) {
157             # HTML with embedded objects, no text alternative
158 4         25 $email = Email::MIME->create(
159             header => $header,
160             attributes => {content_type=>'multipart/related'},
161             parts => [
162             Email::MIME->create(
163             attributes => $body_attributes,
164             body => $html,
165             ),
166             @$html_mime_parts,
167             ],
168             );
169             }
170             elsif ( scalar(@$html_mime_parts) && defined($plain_text_mime) ) {
171             # HTML with embedded objects, with text alternative
172 1         10 $email = Email::MIME->create(
173             header => $header,
174             attributes => {content_type=>'multipart/alternative'},
175             parts => [
176             $plain_text_mime,
177             Email::MIME->create(
178             attributes => {content_type=>'multipart/related'},
179             parts => [
180             Email::MIME->create(
181             attributes => $body_attributes,
182             body => $html,
183             ),
184             @$html_mime_parts,
185             ],
186             ),
187             ],
188             );
189             }
190 7         29164 return $email;
191             }
192              
193             # Add to Email::MIME
194             package Email::MIME;
195              
196 1     1   9 use strict;
  1         1  
  1         49  
197 1     1   5 use Carp;
  1         1  
  1         82  
198 1     1   7 use Email::MIME::Creator;
  1         1  
  1         3188  
199              
200             sub create_html {
201 8     8 0 5009 my ($class, %args) = @_;
202              
203             #Argument checking/defaulting
204 8   33     40 my $html = $args{body} || croak "You must supply a body";
205 8   100     32 my $objects = $args{'objects'} || undef;
206            
207             # Make plain text Email::MIME object, we will never use this alone so we don't need the headers
208 8         14 my $plain_text_mime;
209 8 100       29 if ( exists($args{text_body}) ) {
210 2 50       4 my %text_body_attributes = ( (content_type=>'text/plain'), %{$args{text_body_attributes} || {}} );
  2         19  
211 2         11 $plain_text_mime = $class->create(
212             attributes => \%text_body_attributes,
213             body => $args{text_body},
214             );
215             }
216              
217             # Parse the HTML and create a CID mapping for objects to embed
218 8         1274 my $embedded_cids;
219 8         31 ($html, $embedded_cids) = Email::MIME::CreateHTML::embed_objects($html, \%args);
220              
221             # Create parts for each embedded object
222 7         15 my @html_mime_parts;
223 7 100       31 push @html_mime_parts, Email::MIME::CreateHTML::parts_for_objects($objects, \%args) if ($objects);
224 7 100       35 push @html_mime_parts, Email::MIME::CreateHTML::parts_for_objects($embedded_cids, \%args) if(%$embedded_cids);
225              
226             # Create the mail
227 7         15 my $header = $args{header};
228 7 100       12 my %body_attributes = ( (content_type=>'text/html'), %{$args{body_attributes} || {}});
  7         61  
229 7         31 my $email = Email::MIME::CreateHTML::build_html_email($header, $html, \%body_attributes, \@html_mime_parts, $plain_text_mime);
230 7         112 return $email;
231             }
232              
233             #Log::Trace stubs
234 0     0 0   sub DUMP {}
235 0     0 0   sub TRACE {}
236              
237             1;
238              
239             __END__