removed Bcc options per https://rt.cpan.org/Public/Bug/Display.html?id=75775
[catagits/Catalyst-View-Email.git] / lib / Catalyst / View / Email / Template.pm
1 package Catalyst::View::Email::Template;
2
3 use Moose;
4 use Carp;
5 use Scalar::Util qw/ blessed /;
6 extends 'Catalyst::View::Email';
7
8 our $VERSION = '0.32';
9 $VERSION = eval $VERSION;
10 =head1 NAME
11
12 Catalyst::View::Email::Template - Send Templated Email from Catalyst
13
14 =head1 SYNOPSIS
15
16 Sends templated mail, based upon your default view. It captures the output
17 of the rendering path, slurps in based on mime-types and assembles a multi-part
18 email using L<Email::MIME::Creator> and sends it out.
19
20 =head1 CONFIGURATION
21
22 WARNING: since version 0.10 the configuration options slightly changed!
23
24 Use the helper to create your view:
25     
26     $ script/myapp_create.pl view Email::Template Email::Template
27
28 For basic configuration look at L<Catalyst::View::Email/CONFIGURATION>.
29
30 In your app configuration (example in L<YAML>):
31
32     View::Email::Template:
33         # Optional prefix to look somewhere under the existing configured
34         # template  paths.
35         # Default: none
36         template_prefix: email
37         # Define the defaults for the mail
38         default:
39             # Defines the default view used to render the templates.
40             # If none is specified neither here nor in the stash
41             # Catalysts default view is used.
42             # Warning: if you don't tell Catalyst explicit which of your views should
43             # be its default one, C::V::Email::Template may choose the wrong one!
44             view: TT
45
46 =head1 SENDING EMAIL
47
48 Sending email works just like for L<Catalyst::View::Email> but by specifying 
49 the template instead of the body and forwarding to your Email::Template view:
50
51     sub controller : Private {
52         my ( $self, $c ) = @_;
53
54         $c->stash->{email} = {
55             to          => 'jshirley@gmail.com',
56             cc          => 'abraxxa@cpan.org',
57             from        => 'no-reply@foobar.com',
58             subject     => 'I am a Catalyst generated email',
59             template    => 'test.tt',
60             content_type => 'multipart/alternative'
61         };
62         
63         $c->forward( $c->view('Email::Template') );
64     }
65
66 Alternatively if you want more control over your templates you can use the following idiom
67 to override the defaults. If charset and encoding given, the body become properly encoded.
68
69     templates => [
70         {
71             template        => 'email/test.html.tt',
72             content_type    => 'text/html',
73             charset         => 'utf-8',
74             encoding        => 'quoted-printable',
75             view            => 'TT', 
76         },
77         {
78             template        => 'email/test.plain.mason',
79             content_type    => 'text/plain',
80             charset         => 'utf-8',
81             encoding        => 'quoted-printable',
82             view            => 'Mason', 
83         }
84     ]
85
86
87
88 =head1 HANDLING ERRORS
89
90 See L<Catalyst::View::Email/HANDLING ERRORS>.
91
92 =cut
93
94 # here the defaults of Catalyst::View::Email are extended by the additional
95 # ones Template.pm needs.
96
97 has 'stash_key' => (
98     is      => 'rw',
99     isa     => 'Str',
100     default => sub { "email" },
101     lazy    => 1,
102 );
103
104 has 'template_prefix' => (
105     is      => 'rw',
106     isa     => 'Str',
107     default => sub { '' },
108     lazy    => 1,
109 );
110
111 has 'default' => (
112     is      => 'rw',
113     isa     => 'HashRef',
114     default => sub {
115         {
116             view         => 'TT',
117             content_type => 'text/html',
118         };
119     },
120     lazy => 1,
121 );
122
123 # This view hitches into your default view and will call the render function
124 # on the templates provided.  This means that you have a layer of abstraction
125 # and you aren't required to modify your templates based on your desired engine
126 # (Template Toolkit or Mason, for example).  As long as the view adequately
127 # supports ->render, all things are good.  Mason, and others, are not good.
128
129 #
130 # The path here is to check configuration for the template root, and then
131 # proceed to call render on the subsequent templates and stuff each one
132 # into an Email::MIME container.  The mime-type will be stupidly guessed with
133 # the subdir on the template.
134 #
135
136 # Set it up so if you have multiple parts, they're alternatives.
137 # This is on the top-level message, not the individual parts.
138 #multipart/alternative
139
140 sub _validate_view {
141     my ( $self, $view ) = @_;
142
143     croak "C::V::Email::Template's configured view '$view' isn't an object!"
144       unless ( blessed($view) );
145
146     croak
147       "C::V::Email::Template's configured view '$view' isn't an Catalyst::View!"
148       unless ( $view->isa('Catalyst::View') );
149
150     croak
151 "C::V::Email::Template's configured view '$view' doesn't have a render method!"
152       unless ( $view->can('render') );
153 }
154
155 =head1 METHODS
156
157 =over 4
158
159 =item generate_part
160
161 Generates a MIME part to include in the email. Since the email is template based
162 every template piece is a separate part that is included in the email.
163
164 =cut
165
166 sub generate_part {
167     my ( $self, $c, $attrs ) = @_;
168
169     my $template_prefix      = $self->template_prefix;
170     my $default_view         = $self->default->{view};
171     my $default_content_type = $self->default->{content_type};
172     my $default_charset      = $self->default->{charset}; 
173
174     my $view;
175
176     # use the view specified for the email part
177     if (   exists $attrs->{view}
178         && defined $attrs->{view}
179         && $attrs->{view} ne '' )
180     {
181         $view = $c->view( $attrs->{view} );
182         $c->log->debug(
183             "C::V::Email::Template uses specified view $view for rendering.")
184           if $c->debug;
185     }
186
187     # if none specified use the configured default view
188     elsif ($default_view) {
189         $view = $c->view($default_view);
190         $c->log->debug(
191             "C::V::Email::Template uses default view $view for rendering.")
192           if $c->debug;
193     }
194
195     # else fallback to Catalysts default view
196     else {
197         $view = $c->view;
198         $c->log->debug(
199 "C::V::Email::Template uses Catalysts default view $view for rendering."
200         ) if $c->debug;
201     }
202
203     # validate the per template view
204     $self->_validate_view($view);
205
206     # prefix with template_prefix if configured
207     my $template =
208       $template_prefix ne ''
209       ? join( '/', $template_prefix, $attrs->{template} )
210       : $attrs->{template};
211
212     # setup the attributes (merge with defaults)
213     my $e_m_attrs = $self->SUPER::setup_attributes( $c, $attrs );
214
215     # render the email part
216     my $output = $view->render(
217         $c,
218         $template,
219         {
220             content_type => $e_m_attrs->{content_type},
221             stash_key    => $self->stash_key,
222             %{$c->stash},
223         }
224     );
225     if ( ref $output ) {
226         croak $output->can('as_string') ? $output->as_string : $output;
227     }
228
229     if ( exists $e_m_attrs->{encoding} 
230         && defined $e_m_attrs->{encoding} 
231         && exists $e_m_attrs->{charset} 
232         && defined $e_m_attrs->{charset} ) {
233
234         return Email::MIME->create(
235             attributes => $e_m_attrs,
236             body_str   => $output,
237         );
238
239     } else {
240
241         return Email::MIME->create(
242             attributes => $e_m_attrs,
243             body       => $output,
244         );
245     }
246 }
247
248 =item process
249
250 The process method is called when the view is dispatched to. This creates the
251 multipart message and then sends the message contents off to
252 L<Catalyst::View::Email> for processing, which in turn hands off to
253 L<Email::Sender::Simple>.
254
255 =cut
256
257 around 'process' => sub {
258     my ( $orig, $self, $c, @args ) = @_;
259     my $stash_key = $self->stash_key;
260     return $self->$orig( $c, @args )
261       unless $c->stash->{$stash_key}->{template}
262           or $c->stash->{$stash_key}->{templates};
263
264     # in case of the simple api only one
265     my @parts = ();
266
267     # now find out if the single or multipart api was used
268     # prefer the multipart one
269
270     # multipart api
271     if (   $c->stash->{$stash_key}->{templates}
272         && ref $c->stash->{$stash_key}->{templates} eq 'ARRAY'
273         && ref $c->stash->{$stash_key}->{templates}[0] eq 'HASH' )
274     {
275
276         # loop through all parts of the mail
277         foreach my $part ( @{ $c->stash->{$stash_key}->{templates} } ) {
278             push @parts,
279               $self->generate_part(
280                 $c,
281                 {
282                     view         => $part->{view},
283                     template     => $part->{template},
284                     content_type => $part->{content_type},
285                     charset      => $part->{charset},
286                     encoding     => $part->{encoding},
287                  }
288               );
289         }
290     }
291
292     # single part api
293     elsif ( $c->stash->{$stash_key}->{template} ) {
294         push @parts,
295           $self->generate_part( $c,
296             { template => $c->stash->{$stash_key}->{template}, } );
297     }
298
299     delete $c->stash->{$stash_key}->{body};
300     $c->stash->{$stash_key}->{parts} ||= [];
301     push @{ $c->stash->{$stash_key}->{parts} }, @parts;
302     
303     return $self->$orig($c);
304
305 };
306
307 =back
308
309 =head1 TODO
310
311 =head2 ATTACHMENTS
312
313 There needs to be a method to support attachments.  What I am thinking is
314 something along these lines:
315
316     attachments => [
317         # Set the body to a file handle object, specify content_type and
318         # the file name. (name is what it is sent at, not the file)
319         { body => $fh, name => "foo.pdf", content_type => "application/pdf" },
320         # Or, specify a filename that is added, and hey, encoding!
321         { filename => "foo.gif", name => "foo.gif", content_type => "application/pdf", encoding => "quoted-printable" },
322         # Or, just a path to a file, and do some guesswork for the content type
323         "/path/to/somefile.pdf",
324     ]
325
326 =head1 SEE ALSO
327
328 =head2 L<Catalyst::View::Email> - Send plain boring emails with Catalyst
329
330 =head2 L<Catalyst::Manual> - The Catalyst Manual
331
332 =head2 L<Catalyst::Manual::Cookbook> - The Catalyst Cookbook
333
334 =head1 AUTHORS
335
336 J. Shirley <jshirley@gmail.com>
337
338 Simon Elliott <cpan@browsing.co.uk>
339
340 Alexander Hartmaier <abraxxa@cpan.org>
341
342 =head1 LICENSE
343
344 This library is free software, you can redistribute it and/or modify it under
345 the same terms as Perl itself.
346
347 =cut
348
349 1;