| 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__ |