X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FView%2FEmail.pm;h=730e78ef05bf8090eb316a95868a5c6e2e860ff2;hb=5cf821d32d43491b5336d23310e484908f7a3cc4;hp=b5593a43f4805099768f9b590e0294e5db9d5c15;hpb=229f9fdd4c8e9381750af14937036eb6da0ebe10;p=catagits%2FCatalyst-View-Email.git diff --git a/lib/Catalyst/View/Email.pm b/lib/Catalyst/View/Email.pm index b5593a4..730e78e 100644 --- a/lib/Catalyst/View/Email.pm +++ b/lib/Catalyst/View/Email.pm @@ -1,19 +1,57 @@ package Catalyst::View::Email; -use warnings; -use strict; - -use Class::C3; +use Moose; use Carp; -use Email::Send; +use Encode qw(encode decode); +use Email::Sender::Simple qw/ sendmail /; use Email::MIME::Creator; +extends 'Catalyst::View'; + +our $VERSION = '0.26_01'; +$VERSION = eval $VERSION; + +has 'mailer' => ( + is => 'rw', + isa => 'Str', + lazy => 1, + default => sub { "sendmail" } +); + +has '_mailer_obj' => ( + is => 'rw', + isa => 'Email::Sender::Transport', + lazy => 1, + builder => '_build_mailer_obj', +); -use base qw|Catalyst::View|; +has 'stash_key' => ( + is => 'rw', + isa => 'Str', + lazy => 1, + default => sub { "email" } +); -our $VERSION = '0.05'; +has 'default' => ( + is => 'rw', + isa => 'HashRef', + default => sub { { content_type => 'text/plain' } }, + lazy => 1, +); -__PACKAGE__->mk_accessors(qw(sender stash_key content_type mailer)); +has 'sender' => ( + is => 'rw', + isa => 'HashRef', + lazy => 1, + default => sub { { mailer => shift->mailer } } +); + +has 'content_type' => ( + is => 'rw', + isa => 'Str', + default => sub { shift->default->{content_type} }, + lazy => 1, +); =head1 NAME @@ -21,58 +59,94 @@ Catalyst::View::Email - Send Email from Catalyst =head1 SYNOPSIS -This module simply sends out email from a stash key specified in the +This module sends out emails from a stash key specified in the configuration settings. =head1 CONFIGURATION -In your app configuration (example in L): +WARNING: since version 0.10 the configuration options slightly changed! - View::Email: - stash_key: email - content_type: text/plain - sender: - method: SMTP - # mailer_args is passed directly into Email::Send - mailer_args: - Host: smtp.example.com # defaults to localhost - username: username - password: password +Use the helper to create your View: + + $ script/myapp_create.pl view Email Email + +In your app configuration: + + __PACKAGE__->config( + 'View::Email' => { + # Where to look in the stash for the email information. + # 'email' is the default, so you don't have to specify it. + stash_key => 'email', + # Define the defaults for the mail + default => { + # Defines the default content type (mime type). Mandatory + content_type => 'text/plain', + # Defines the default charset for every MIME part with the + # content type text. + # According to RFC2049 a MIME part without a charset should + # be treated as US-ASCII by the mail client. + # If the charset is not set it won't be set for all MIME parts + # without an overridden one. + # Default: none + charset => 'utf-8' + }, + # Setup how to send the email + # all those options are passed directly to Email::Sender::Simple + sender => { + # if mailer doesn't start with Email::Sender::Simple::Transport::, + # then this is prepended. + mailer => 'SMTP', + # mailer_args is passed directly into Email::Sender::Simple + mailer_args => { + Host => 'smtp.example.com', # defaults to localhost + username => 'username', + password => 'password', + } + } + } + ); -=head2 NOTE ON SMTP +=head1 NOTE ON SMTP -If you use SMTP and don't specify Host, it will default to localhost and -attempt delivery. This often times means an email will sit in a queue -somewhere and not be delivered. +If you use SMTP and don't specify host, it will default to localhost and +attempt delivery. This often means an email will sit in a queue and +not be delivered. =cut -__PACKAGE__->config( - stash_key => 'email', -); - =head1 SENDING EMAIL -In your controller, simply forward to the view after populating the C +Sending email is just filling the stash and forwarding to the view: sub controller : Private { my ( $self, $c ) = @_; + $c->stash->{email} = { - to => q{catalyst@rocksyoursocks.com}, - from => q{no-reply@socksthatarerocked.com}, - subject => qq{Your Subject Here}, - body => qq{Body Body Body} + to => 'jshirley@gmail.com', + cc => 'abraxxa@cpan.org', + bcc => join ',', qw/hidden@secret.com hidden2@foobar.com/, + from => 'no-reply@foobar.com', + subject => 'I am a Catalyst generated email', + body => 'Body Body Body', }; - $c->forward('View::Email'); + + $c->forward( $c->view('Email') ); } -Alternatively, you can use a more raw interface, and specify the headers as -an array reference. +Alternatively you can use a more raw interface and specify the headers as +an array reference like it is passed to L. +Note that you may also mix both syntaxes if you like ours better but need to +specify additional header attributes. +The attributes are appended to the header array reference without overwriting +contained ones. $c->stash->{email} = { header => [ - To => 'foo@bar.com', - Subject => 'Note the capitalization differences' + To => 'jshirley@gmail.com', + Cc => 'abraxxa@cpan.org', + Bcc => join ',', qw/hidden@secret.com hidden2@foobar.com/, + From => 'no-reply@foobar.com', + Subject => 'Note the capitalization differences', ], body => qq{Ain't got no body, and nobody cares.}, # Or, send parts @@ -83,119 +157,228 @@ an array reference. disposition => 'attachment', charset => 'US-ASCII', }, - body => qq{Got a body, but didn't get ahead.} + body => qq{Got a body, but didn't get ahead.}, ) ], }; -=head1 HANDLING FAILURES +=head1 HANDLING ERRORS -If the email fails to send, the view will die (throw an exception). After -your forward to the view, it is a good idea to check for errors: +If the email fails to send, the view will die (throw an exception). +After your forward to the view, it is a good idea to check for errors: + + $c->forward( $c->view('Email') ); - $c->forward('View::Email'); if ( scalar( @{ $c->error } ) ) { $c->error(0); # Reset the error condition if you need to - $c->res->body('Oh noes!'); + $c->response->body('Oh noes!'); } else { - $c->res->body('Email sent A-OK! (At least as far as we can tell)'); + $c->response->body('Email sent A-OK! (At least as far as we can tell)'); } -=head1 OTHER MAILERS +=head1 USING TEMPLATES FOR EMAIL + +Now, it's no fun to just send out email using plain strings. +Take a look at L to see how you can use your +favourite template engine to render the mail body. + +=head1 METHODS -Now, it's no fun to just send out email using plain strings. We also -have L for use. You can also toggle -this as being used by setting up your configuration to look like this: +=over 4 - View::Email: - default_view: TT +=item new -Then, Catalyst::View::Email will forward to your View::TT by default. +Validates the base config and creates the L object for later use +by process. =cut -sub new { - my $self = shift->next::method(@_); +sub BUILD { + my $self = shift; - my ( $c, $arguments ) = @_; + my $stash_key = $self->stash_key; + croak "$self stash_key isn't defined!" + if ( $stash_key eq '' ); - my $mailer = Email::Send->new; +} - if ( my $method = $self->sender->{method} ) { - croak "$method is not supported, see Email::Send" - unless $mailer->mailer_available($method); - $mailer->mailer($method); - } else { - # Default case, run through the most likely options first. - for ( qw/SMTP Sendmail Qmail/ ) { - $mailer->mailer($_) and last if $mailer->mailer_available($_); - } - } +sub _build_mailer_obj { + my ($self) = @_; + my $transport_class = ucfirst $self->sender->{mailer}; - if ( my $args = $self->sender->{mailer_args} ) { - if ( ref $args eq 'HASH' ) { - $mailer->mailer_args([ %$args ]); - } - elsif ( ref $args eq 'ARRAY' ) { - $mailer->mailer_args($args); - } else { - croak "Invalid mailer_args specified, check pod for Email::Send!"; - } + # borrowed from Email::Sender::Simple -- apeiron, 2010-01-26 + if ( $transport_class !~ /^Email::Sender::Transport::/ ) { + $transport_class = "Email::Sender::Transport::$transport_class"; } - $self->mailer($mailer); + Class::MOP::load_class($transport_class); - return $self; + return $transport_class->new( $self->sender->{mailer_args} || {} ); } +=item process($c) + +The process method does the actual processing when the view is dispatched to. + +This method sets up the email parts and hands off to L to handle +the actual email delivery. + +=cut + sub process { my ( $self, $c ) = @_; croak "Unable to send mail, bad mail configuration" - unless $self->mailer; + unless $self->sender->{mailer}; - my $email = $c->stash->{$self->stash_key}; + my $email = $c->stash->{ $self->stash_key }; croak "Can't send email without a valid email structure" - unless $email; - - if ( $self->content_type ) { - $email->{content_type} ||= $self->content_type; + unless $email; + + # Default content type + if ( $self->content_type and not $email->{content_type} ) { + $email->{content_type} = $self->content_type; } - my $header = $email->{header} || []; - push @$header, ('To' => delete $email->{to}) - if $email->{to}; - push @$header, ('From' => delete $email->{from}) - if $email->{from}; - push @$header, ('Subject' => delete $email->{subject}) - if $email->{subject}; - push @$header, ('Content-type' => delete $email->{content_type}) - if $email->{content_type}; + my $header = $email->{header} || []; + push @$header, ( 'To' => delete $email->{to} ) + if $email->{to}; + push @$header, ( 'Cc' => delete $email->{cc} ) + if $email->{cc}; + push @$header, ( 'Bcc' => delete $email->{bcc} ) + if $email->{bcc}; + push @$header, ( 'From' => delete $email->{from} ) + if $email->{from}; + push @$header, + ( 'Subject' => Encode::encode( 'MIME-Header', delete $email->{subject} ) ) + if $email->{subject}; + push @$header, ( 'Content-type' => $email->{content_type} ) + if $email->{content_type}; my $parts = $email->{parts}; my $body = $email->{body}; - + unless ( $parts or $body ) { croak "Can't send email without parts or body, check stash"; } - my %mime = ( header => $header ); + my %mime = ( header => $header, attributes => {} ); if ( $parts and ref $parts eq 'ARRAY' ) { $mime{parts} = $parts; - } else { + } + else { $mime{body} = $body; } - my $message = Email::MIME->create(%mime); + $mime{attributes}->{content_type} = $email->{content_type} + if $email->{content_type}; + if ( $mime{attributes} + and not $mime{attributes}->{charset} + and $self->{default}->{charset} ) + { + $mime{attributes}->{charset} = $self->{default}->{charset}; + } + + my $message = $self->generate_message( $c, \%mime ); - if ( $message ) { - $self->mailer->send($message); - } else { + if ($message) { + my $return = sendmail( $message, { transport => $self->_mailer_obj } ); + + # return is a Return::Value object, so this will stringify as the error + # in the case of a failure. + croak "$return" if !$return; + } + else { croak "Unable to create message"; } } +=item setup_attributes($c, $attr) + +Merge attributes with the configured defaults. You can override this method to +return a structure to pass into L which subsequently +passes the return value of this method to Email::MIME->create under the +C key. + +=cut + +sub setup_attributes { + my ( $self, $c, $attrs ) = @_; + + my $default_content_type = $self->default->{content_type}; + my $default_charset = $self->default->{charset}; + + my $e_m_attrs = {}; + + if ( exists $attrs->{content_type} + && defined $attrs->{content_type} + && $attrs->{content_type} ne '' ) + { + $c->log->debug( 'C::V::Email uses specified content_type ' + . $attrs->{content_type} + . '.' ) + if $c->debug; + $e_m_attrs->{content_type} = $attrs->{content_type}; + } + elsif ( defined $default_content_type && $default_content_type ne '' ) { + $c->log->debug( + "C::V::Email uses default content_type $default_content_type.") + if $c->debug; + $e_m_attrs->{content_type} = $default_content_type; + } + + if ( exists $attrs->{charset} + && defined $attrs->{charset} + && $attrs->{charset} ne '' ) + { + $e_m_attrs->{charset} = $attrs->{charset}; + } + elsif ( defined $default_charset && $default_charset ne '' ) { + $e_m_attrs->{charset} = $default_charset; + } + + return $e_m_attrs; +} + +=item generate_message($c, $attr) + +Generate a message part, which should be an L object and return it. + +Takes the attributes, merges with the defaults as necessary and returns a +message object. + +=cut + +sub generate_message { + my ( $self, $c, $attr ) = @_; + + # setup the attributes (merge with defaultis) + $attr->{attributes} = $self->setup_attributes( $c, $attr->{attributes} ); + Email::MIME->create( %$attr ); +} + +=back + + +=head1 TROUBLESHOOTING + +As with most things computer related, things break. Email even more so. +Typically any errors are going to come from using SMTP as your sending method, +which means that if you are having trouble the first place to look is at +L. This module is just a wrapper for L, +so if you get an error on sending, it is likely from there anyway. + +If you are using SMTP and have troubles sending, whether it is authentication +or a very bland "Can't send" message, make sure that you have L and, +if applicable, L installed. + +It is very simple to check that you can connect via L, and if you +do have sending errors the first thing to do is to write a simple script +that attempts to connect. If it works, it is probably something in your +configuration so double check there. If it doesn't, well, keep modifying +the script and/or your mail server configuration until it does! + =head1 SEE ALSO =head2 L - Send fancy template emails with Cat @@ -208,6 +391,8 @@ sub process { J. Shirley +Alexander Hartmaier + =head1 CONTRIBUTORS (Thanks!) @@ -216,7 +401,21 @@ Matt S Trout Daniel Westermann-Clark -Simon Elliott - ::Template +Simon Elliott + +Roman Filippov + +Lance Brown + +Devin Austin + +Chris Nehren + +=head1 COPYRIGHT + +Copyright (c) 2007 - 2009 +the Catalyst::View::Email L and L +as listed above. =head1 LICENSE