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