1use warnings; 2use strict; 3 4package Jifty::Notification; 5 6use base qw/Jifty::Object Class::Accessor::Fast/; 7use Email::Send (); 8use Email::MIME::CreateHTML; 9 10__PACKAGE__->mk_accessors( 11 qw/body html_body preface footer subject from _recipients _to_list to cc bcc/); 12 13=head1 NAME 14 15Jifty::Notification - Send emails from Jifty 16 17=head1 USAGE 18 19It is recommended that you subclass L<Jifty::Notification> and 20override C<body>, C<html_body>, C<subject>, C<recipients>, and C<from> 21for each message. (You may want a base class to provide C<from>, 22C<preface> and C<footer> for example.) This lets you keep all of your 23notifications in the same place. 24 25However, if you really want to make a notification type in code 26without subclassing, you can create a C<Jifty::Notification> and call 27the C<set_body>, C<set_subject>, and so on methods on it. 28 29=head1 METHODS 30 31=cut 32 33=head2 new [KEY1 => VAL1, ...] 34 35Creates a new L<Jifty::Notification>. Any keyword args given are used 36to call set accessors of the same name. 37 38Then it calls C<setup>. 39 40=cut 41 42sub new { 43 my $class = shift; 44 my $self = bless {}, $class; 45 46 my %args = @_; 47 48 # initialize message bits to avoid 'undef' warnings 49 #for (qw(body preface footer subject)) { $self->$_(''); } 50 $self->_recipients( [] ); 51 52 while ( my ( $arg, $value ) = each %args ) { 53 if ( $self->can($arg) ) { 54 $self->$arg($value); 55 } else { 56 $self->log->error( 57 ( ref $self ) . " called with invalid argument $arg" ); 58 } 59 } 60 61 $self->setup; 62 63 return $self; 64} 65 66=head2 setup 67 68Your subclass should override this to set the various field values. 69 70=cut 71 72sub setup { } 73 74=head2 send_one_message 75 76Delivers the notification, using the L<Email::Send> mailer defined in 77the C<Mailer> and C<MailerArgs> configuration arguments. Returns true 78if mail was actually sent. Note errors are not the only cause of mail 79not being sent -- for example, the recipients list could be empty. 80 81If you wish to send HTML mail, set C<html_body>. If this is not set 82(for backwards compatibility) a plain-text email is sent. If 83C<html_body> and C<body> are both set, a multipart mail is sent. See 84L<Email::MIME::CreateHTML> for how this is done. 85 86Be aware that if you haven't set C<recipients>, this will fail 87silently and return without doing anything useful. 88 89=cut 90 91sub send_one_message { 92 my $self = shift; 93 my @recipients = $self->recipients; 94 my $to = join( ', ', 95 map { ( ref $_ && $_->can('email') ? $_->email : $_ ) } grep {$_} @recipients ); 96 $self->log->debug("Sending a ".ref($self)." to $to"); 97 return unless ($to); 98 my $message = ""; 99 my $appname = Jifty->config->framework('ApplicationName'); 100 101 my %attrs = ( charset => 'UTF-8' ); 102 103 my $from = Encode::encode( 104 'MIME-Header', 105 $self->from || _('%1 <%2>' , $appname, Jifty->config->framework('AdminEmail')) 106 ); 107 my $subj = Encode::encode( 108 'MIME-Header', 109 $self->subject || _("A notification from %1!",$appname ) 110 ); 111 112 my $cc = Encode::encode('MIME-Header', $self->cc || ''); 113 my $bcc = Encode::encode('MIME-Header', $self->bcc || ''); 114 115 if ( defined $self->html_body ) { 116 117 # Email::MIME takes _bytes_, not characters, for the "body" 118 # argument, so we need to encode the full_body into UTF8. 119 # Modern Email::MIME->create takes a "body_str" argument which 120 # does the encoding for us, but Email::MIME::CreateHTML 121 # doesn't grok it. See also L</parts> for the other location 122 # which does the encode. 123 $message = Email::MIME->create_html( 124 header => [ 125 From => $from, 126 To => $to, 127 Cc => $cc, 128 Bcc => $bcc, 129 Subject => $subj, 130 ], 131 attributes => \%attrs, 132 text_body_attributes => \%attrs, 133 body_attributes => \%attrs, 134 text_body => Encode::encode_utf8( $self->full_body ), 135 body => Encode::encode_utf8( $self->full_html ), 136 embed => 0, 137 inline_css => 0, 138 ); 139 140 # Since the containing messsage will still be us-ascii otherwise 141 $message->charset_set( $attrs{'charset'} ); 142 } else { 143 $message = Email::MIME->create( 144 header => [ 145 From => $from, 146 To => $to, 147 Cc => $cc, 148 Bcc => $bcc, 149 Subject => $subj, 150 ], 151 attributes => \%attrs, 152 parts => $self->parts, 153 ); 154 } 155 $message->encoding_set('8bit') 156 if ( scalar $message->parts == 1 ); 157 $self->set_headers($message); 158 159 my $method = Jifty->config->framework('Mailer'); 160 my $args_ref = Jifty->config->framework('MailerArgs'); 161 $args_ref = [] unless defined $args_ref; 162 163 my $sender 164 = Email::Send->new( { mailer => $method, mailer_args => $args_ref } ); 165 166 my $ret = $sender->send($message); 167 168 unless ($ret) { 169 $self->log->error("Error sending mail: $ret"); 170 } 171 172 $ret; 173} 174 175=head2 set_headers MESSAGE 176 177Takes a L<Email::MIME> object C<MESSAGE>, and modifies it as 178necessary before sending it out. As the method name implies, this is 179usually used to add or modify headers. By default, does nothing; this 180method is meant to be overridden. 181 182=cut 183 184sub set_headers {} 185 186=head2 body [BODY] 187 188Gets or sets the body of the notification, as a string. 189 190=head2 subject [SUBJECT] 191 192Gets or sets the subject of the notification, as a string. 193 194=head2 from [FROM] 195 196Gets or sets the from address of the notification, as a string. 197 198=head2 recipients [RECIPIENT, ...] 199 200Gets or sets the addresses of the recipients of the notification, as a 201list of strings (not a reference). 202 203=cut 204 205sub recipients { 206 my $self = shift; 207 $self->_recipients( [@_] ) if @_; 208 return @{ $self->_recipients }; 209} 210 211=head2 email_from OBJECT 212 213Returns the email address from the given object. This defaults to 214calling an 'email' method on the object. This method will be called 215by L</send> to get email addresses (for L</to>) out of the list of 216L</recipients>. 217 218=cut 219 220sub email_from { 221 my $self = shift; 222 my ($obj) = @_; 223 if ( $obj->can('email') ) { 224 return $obj->email; 225 } else { 226 die "No 'email' method on " . ref($obj) . "; override 'email_from'"; 227 } 228} 229 230=head2 to_list [OBJECT, OBJECT...] 231 232Gets or sets the list of objects that the message will be sent to. 233Each one is sent a separate copy of the email. If passed no 234parameters, returns the objects that have been set. This also 235suppresses duplicates. 236 237=cut 238 239sub to_list { 240 my $self = shift; 241 if (@_) { 242 my %ids = (); 243 $ids{ $self->to->id } = undef if $self->to; 244 $ids{ $_->id } = $_ for @_; 245 $self->_to_list( [ grep defined, values %ids ] ); 246 } 247 return @{ $self->_to_list || [] }; 248} 249 250=head2 send 251 252Sends an individual email to every user in L</to_list>; it does this by 253setting L</to> and L</recipient> to the first user in L</to_list> 254calling L<Jifty::Notification>'s C<send> method, and progressing down 255the list. 256 257Additionally, if L</to> was set elsewhere, sends an email to that 258person, as well. 259 260=cut 261 262sub send { 263 my $self = shift; 264 my $currentuser_object_class = Jifty->app_class("CurrentUser"); 265 for my $to ( grep {defined} ($self->to, $self->to_list) ) { 266 if ($to->can('id')) { 267 next if $currentuser_object_class->can("nobody") 268 and $currentuser_object_class->nobody->id 269 and $to->id == $currentuser_object_class->nobody->id; 270 271 next if $to->id == $currentuser_object_class->superuser->id; 272 } 273 $self->to($to); 274 $self->recipients($to); 275 $self->send_one_message(@_); 276 } 277} 278 279=head2 to 280 281Of the list of users that C<to> provided, returns the one which mail 282is currently being sent to. This is set by the L</send> method, such 283that it is available to all of the methods that 284L<Jifty::Notification>'s C<send> method calls. 285 286=cut 287 288=head2 preface 289 290Print a header for the message. You want to override this to print a message. 291 292Returns the message as a scalar. 293 294=cut 295 296=head2 footer 297 298Print a footer for the message. You want to override this to print a message. 299 300Returns the message as a scalar. 301 302=cut 303 304=head2 full_body 305 306The main, plain-text part of the message. This is the preface, 307body, and footer joined by newlines. 308 309=cut 310 311sub full_body { 312 my $self = shift; 313 return join( "\n", grep { defined } $self->preface, $self->body, $self->footer ); 314} 315 316=head2 full_html 317 318Same as full_body, but with HTML. 319 320=cut 321 322sub full_html { 323 my $self = shift; 324 return join( "\n", grep { defined } $self->preface, $self->html_body, $self->footer ); 325} 326 327=head2 parts 328 329The parts of the message. You want to override this if you want to 330send multi-part mail. By default, this method returns a single 331part consisting of the result of calling C<< $self->full_body >>. 332 333Returns the parts as an array reference. 334 335=cut 336 337sub parts { 338 my $self = shift; 339# NOTICE: we should keep string in perl string (with utf8 flag) 340# rather then encode it into octets. Email::MIME would call Encode::encode in 341# its create function. 342 return [ Email::MIME->create( 343 attributes => { charset => 'UTF-8' }, 344 body => Encode::encode_utf8( $self->full_body ), 345 ) ]; 346} 347 348=head2 magic_letme_token_for PATH 349 350Returns a L<Jifty::LetMe> token which allows the current user to access a path on the 351site. 352 353=cut 354 355sub magic_letme_token_for { 356 my $self = shift; 357 my $path = shift; 358 my %args = @_; 359 360 my $letme = Jifty::LetMe->new(); 361 $letme->email( $self->to->email ); 362 $letme->path($path); 363 $letme->args( \%args ); 364 return ( $letme->as_url ); 365} 366 3671; 368