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