File Coverage

blib/lib/Template/Plugin/MIME.pm
Criterion Covered Total %
statement 66 74 89.1
branch 6 12 50.0
condition 3 7 42.8
subroutine 18 20 90.0
pod 5 6 83.3
total 98 119 82.3


line stmt bran cond sub pod time code
1             package Template::Plugin::MIME;
2              
3 6     6   435796 use warnings;
  6         17  
  6         302  
4 6     6   37 use strict;
  6         12  
  6         258  
5              
6 6     6   39 use base qw( Template::Plugin::Procedural );
  6         23  
  6         15184  
7              
8 6     6   81578 use MIME::Entity;
  6         1152460  
  6         227  
9 6     6   81 use MIME::Base64;
  6         13  
  6         501  
10 6     6   6386 use Sys::Hostname;
  6         8552  
  6         385  
11 6     6   7553 use Digest::SHA;
  6         54643  
  6         595  
12 6     6   8775 use Try::Tiny;
  6         14022  
  6         647  
13 6     6   56 use Carp;
  6         15  
  6         549  
14              
15             BEGIN {
16             try {
17 6         4484 require File::LibMagic;
18 6     6   111 };
19             }
20              
21             =head1 NAME
22              
23             Template::Plugin::MIME - TemplateToolkit plugin providing a interface to MIME::Entity
24              
25             =head1 VERSION
26              
27             Version 0.12
28              
29             =cut
30              
31             our $VERSION = '0.12';
32              
33             our $NAME = __PACKAGE__;
34              
35             =head1 SYNOPSIS
36              
37             Use this plugin inside a template:
38              
39             [% USE MIME %]
40            
41             [% cid_of_image = MIME.attach('image.png') %]
42            
43            
44              
45             =cut
46              
47             sub load($$) {
48 5     5 1 328345 my ($class, $context) = @_;
49 5         36 bless {
50             _CONTEXT => $context,
51             }, $class;
52             }
53              
54             sub new($$$) {
55 5     5 1 65 my ($self, $context, $params) = @_;
56 5 50       29 unless (ref $self) {
57 0         0 croak "cannot instanciate myself, consider using $self->load!";
58             }
59 5   50     87 $context->{$NAME} = {
60             attachments => {
61             all => [],
62             index => {
63             files => {},
64             cids => {},
65             }
66             },
67             hostname => $params->{hostname} || hostname || 'localhost',
68             };
69             try {
70 5     5   252 $self->{magic} = File::LibMagic->new;
71 5         111 };
72 5         73 return $self;
73             }
74              
75 8     8   109 sub _context($) { shift()->{_CONTEXT} }
76              
77             sub base64($$) {
78 0     0 0 0 return encode_base64($_[1]);
79             }
80              
81             =head1 METHODS FOR USE OUTSIDE TEMPLATE
82              
83             =cut
84              
85             =head2 C<< attachments() >>
86              
87             Returns all attached files.
88              
89             use Template;
90             use Template::Plugin::MIME;
91            
92             $template = Template->new;
93             $template->process(...);
94            
95             $attachments = Template::Plugin::MIME->attachments($template);
96              
97             =cut
98              
99             sub attachments($$) {
100 2     2 1 6 my ($self, $template) = @_;
101 2         10 my $context = $template->context;
102 2         17 return $context->{$NAME}->{attachments}->{all};
103             }
104              
105             =head2 C<< merge($template, $mail) >>
106              
107             This method merges a L documents together with all attached files in the template.
108              
109             use Template;
110             use Template::Plugin::MIME;
111             use MIME::Entity;
112            
113             $template = Template->new;
114             $template->process($infile, $stash, $outfile);
115            
116             $entity = MIME::Entity->build(
117             From => ...,
118             To => ...,
119             Subject => ...,
120             Type => 'text/html',
121             Path => $outfile,
122             );
123            
124             Template::Plugin::MIME->merge($template, $entity);
125             # $entity is now multipart/related
126              
127             This methods invokes C< make_multipart('related') > on C< $entity > an then attaches all party to this entity with C< add_part() >.
128              
129             A more complex example is shown below. This can be used when you want seperate attachement dispositions together:
130              
131             use Template;
132             use Template::Plugin::MIME;
133             use MIME::Entity;
134            
135             $template = Template->new;
136             $template->process($ttfile, $stash, $outfile);
137            
138             $inner_text = MIME::Entity->build(
139             Top => 0, # this is very important!
140             Type => 'text/plain',
141             Path => $plainfile,
142             );
143            
144             $inner_html = MIME::Entity->build(
145             Top => 0, # this is very important!
146             Type => 'text/html',
147             Path => $outfile,
148             );
149            
150             $outer = MIME::Entity->build(
151             From => ...,
152             To => ...,
153             Subject => ...,
154             Type => 'multipart/alternative',
155             );
156            
157             # first, merges the attachments into the html entity:
158             Template::Plugin::MIME->merge($template, $inner_html);
159             # $inner_html is now multipart/related
160            
161             # seconds merges the alternative into the root entity:
162             $outer->add_part($inner_text);
163             $outer->add_part($inner_html);
164              
165             The advantage is, the root entity considers of two alternative: a plain text and a html variant. the html variant is a multipart too, with related content (images, ...) attached.
166              
167             And a total complex example shows how to use mixed content:
168              
169             use Template;
170             use Template::Plugin::MIME;
171             use MIME::Entity;
172            
173             $template = Template->new;
174             $template->process($ttfile, $stash, $outfile);
175            
176             $inner_text = MIME::Entity->build(
177             Top => 0, # this is very important!
178             Type => 'text/plain',
179             Path => $plainfile,
180             );
181            
182             $inner_html = MIME::Entity->build(
183             Top => 0, # this is very important!
184             Type => 'text/html',
185             Path => $outfile,
186             );
187            
188             $outer = MIME::Entity->build(
189             Top => 0, # this is very important!
190             Type => 'multipart/alternative',
191             );
192            
193             $entity = MIME::Entity->build(
194             From => ...,
195             To => ...,
196             Subject => ...,
197             Type => 'multipart/mixed',
198             );
199            
200             # first, merge the attachments into the html entity:
201             Template::Plugin::MIME->merge($template, $inner_html);
202             # $inner_html is now multipart/related
203            
204             # second, merge the alternative into the outer entity:
205             $outer->add_part($inner_text);
206             $outer->add_part($inner_html);
207            
208             # third, merge all parts together the root entity
209             $entity->add_part($outer);
210             $entity->add_part(Path => 'invoice.pdf', Type => 'application/pdf', Filename => 'Your Invoice.pdf');
211              
212             The mime structue is now
213              
214             (root) multipart/mixed
215             |-> multipart/alternative
216             | |-> multipart/related
217             | | |-> text/html
218             | | `-> image/png
219             | `-> text/plain
220             `-> application/pdf
221              
222             =cut
223              
224             sub merge {
225 2     2 1 5735 my ($self, $template, $mail) = @_;
226 2         18 my $context = $template->context;
227 2         24 my $attachments = $self->attachments($template);
228            
229 2         14 $mail->make_multipart('related');
230            
231 2         10230 foreach my $attachment (@$attachments) {
232 2         11 $mail->add_part($attachment);
233             }
234            
235 2         34 return $mail;
236             }
237            
238             =head1 FUNCTIONS FOR USE INSIDE TEMPLATE
239              
240             =head2 C<< attach($path [, %options] ) >>
241              
242             This method attaches a file and returns a Content-Id for use within html content, for example.
243              
244             [% USE MIME %]
245            
246             [% signature_cid = MIME.attach("signature.png") %]
247            
248            
249              
250             The paramhash C<%options> is equivalent to the C class/instance method in L. The following options are overridden in order to work with related content:
251              
252             =over 4
253              
254             =item * C< Path > is equivalent to C< $path >
255              
256             =item * C< Id > is the content-id, automatically generated.
257              
258             =item * C< Encoding > is forced to Base64.
259              
260             =item * C< Type > is the content type (but see below for more information)
261              
262             =item * C< Top > is 0, since an attachment is not a top-level entity.
263              
264             =back
265              
266             All other options are passed through.
267              
268             =head3 Obtaining Content-Type
269              
270             If the Options C< Type > is set, this will be used regardless what the file is really is.
271              
272             If L is installed on your system, C will be invoked to obtain the mime-type. This method may fail and error messages are discarded for now.
273              
274             If all fails, the mime-type "application/octet-stream" is used.
275              
276             =cut
277              
278             sub attach($$;$) {
279 4     4 1 75 my ($self, $path, $options) = @_;
280 4         18 my $context = $self->_context;
281 4         12 my $this = $context->{$NAME};
282            
283 4 50       22 if (exists $this->{attachments}->{index}->{files}->{$path}) {
284 0         0 return $this->{attachments}->{index}->{files}->{$path}->head->get('Content-Id');
285             }
286            
287 4 50       114 unless (-e $path) {
288 0         0 croak "file '$path' does not exists!";
289             }
290            
291 4         44 my $digest = Digest::SHA->new(256);
292 4 50       151 $digest->addfile($path) or die $!;
293 4         604 my $cid = $digest->hexdigest . '@' . $this->{hostname};
294            
295 4 50       27 if (exists $this->{attachments}->{index}->{cids}->{$cid}) {
296 0         0 $this->{attachments}->{index}->{files}->{$path} = $this->{attachments}->{index}->{cids}->{$cid};
297 0         0 return $cid;
298             }
299            
300 4         13 my $mimetype = $options->{Type};
301            
302             try {
303 4 50   4   167 return unless defined $self->{magic};
304 0   0     0 $mimetype ||= $self->{magic}->checktype_filename($path);
305             } catch {
306 0     0   0 carp "libmagic: $_";
307 4         43 };
308            
309 4   100     115 $mimetype ||= 'application/octet-stream';
310            
311 4         60 my $part = MIME::Entity->build(
312             %$options,
313             Path => $path,
314             Id => $cid,
315             Encoding => 'base64',
316             Type => $mimetype,
317             Top => 0
318             );
319 4         7241 push @{ $this->{attachments}->{all} } => $part;
  4         18  
320 4         68 $this->{attachments}->{index}->{cids}->{$cid} = $this->{attachments}->{index}->{files}->{$path} = $part;
321 4         75 return $cid;
322             }
323              
324             =head1 AUTHOR
325              
326             David Zurborg, C<< >>
327              
328             =head1 BUGS
329              
330             Please report any bugs or feature requests to C, or through
331             the web interface at L. I will be notified, and then you'll
332             automatically be notified of progress on your bug as I make changes.
333              
334             =head1 SUPPORT
335              
336             You can find documentation for this module with the perldoc command.
337              
338             perldoc Template::Plugin::MIME
339              
340              
341             You can also look for information at:
342              
343             =over 4
344              
345             =item * RT: CPAN's request tracker
346              
347             L
348              
349             =item * AnnoCPAN: Annotated CPAN documentation
350              
351             L
352              
353             =item * CPAN Ratings
354              
355             L
356              
357             =item * Search CPAN
358              
359             L
360              
361             =back
362              
363              
364             =head1 ACKNOWLEDGEMENTS
365              
366              
367             =head1 COPYRIGHT & LICENSE
368              
369             Copyright 2013 David Zurborg, all rights reserved.
370              
371             This program is free software; you can redistribute it and/or modify it under the terms of the ISC license.
372              
373             =cut
374              
375             1; # End of Template::Plugin::MIME