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