File Coverage

blib/lib/App/Framework/Feature/Mail.pm
Criterion Covered Total %
statement 32 91 35.1
branch 3 32 9.3
condition 1 18 5.5
subroutine 7 8 87.5
pod 5 5 100.0
total 48 154 31.1


line stmt bran cond sub pod time code
1             package App::Framework::Feature::Mail ;
2              
3             =head1 NAME
4              
5             App::Framework::Feature::Mail - Send mail
6              
7             =head1 SYNOPSIS
8              
9             use App::Framework '+Mail' ;
10              
11              
12             =head1 DESCRIPTION
13              
14             Provides a simplified mail interface, and application error auto-mailing.
15              
16             When used as a mail interface, this feature is accessed in the same manner as any other (e.g. see L).
17              
18             The accessor function (L) returns the mail object if no parameters are specified; otherwise it will send a mail:
19              
20             $app->Mail("This is a test",
21             'from' => 'someone@domain.co.uk',
22             'to' => 'afriend@domain.com',
23             'subject' => 'a test',
24             ) ;
25            
26             Default settings may be set at the start of an application so that only specific parameters need to be added:
27              
28             $app->Mail()->set(
29             'from' => 'someone@domain.co.uk',
30             'error_to' => 'someone@domain.co.uk',
31             'err_level' => 'warning',
32             ) ;
33              
34             ## send a mail to 'afriend@domain.com'
35             $app->Mail("This is a test",
36             'to' => 'afriend@domain.com',
37             'subject' => 'a test',
38             ) ;
39              
40             ...
41            
42             ## send another - still goers to 'afriend@domain.com'
43             $app->Mail("This is another test",
44             'subject' => 'another test',
45             ) ;
46              
47             An additional capability is that this feature can automatically send emails of any errors. To do this you first of all
48             need to specify the 'error_to' recipient, and then set the 'err_level'. The 'err_level' setting specifies the type of
49             error that will generate an email. For example, setting 'err_level' to "warning" means all warnings AND errors will result
50             in emails; but notes will not (see L for types).
51              
52             This feature also automatically adds mail-related command line options to allow a user to specify the field settings for themselves
53             (or an application may over ride with their own defaults).
54              
55             Note that the 'to' and 'error_to' fields may be a comma seperated list of mail recipients.
56              
57             =cut
58              
59 2     2   4613 use strict ;
  2         2  
  2         61  
60 2     2   10 use Carp ;
  2         2  
  2         172  
61              
62             our $VERSION = "1.002" ;
63              
64              
65             #============================================================================================
66             # USES
67             #============================================================================================
68 2     2   10 use App::Framework::Feature ;
  2         3  
  2         1693  
69              
70             #============================================================================================
71             # OBJECT HIERARCHY
72             #============================================================================================
73             our @ISA = qw(App::Framework::Feature) ;
74              
75             #============================================================================================
76             # GLOBALS
77             #============================================================================================
78              
79             =head2 ADDITIONAL COMMAND LINE OPTIONS
80              
81             This extension adds the following additional command line options to any application:
82              
83             =over 4
84              
85             =item B<-mail-from> - Mail sender (required)
86              
87             Email sender
88              
89             =item B<-mail-to> - Mail recipient(s) (required)
90              
91             Email recipient. Where there are multiple recipients, they should be set as a comma seperated list of email addresses
92              
93             =item B<-mail-error-to> - Error mail recipient(s)
94              
95             Email recipient for errors. If set, program errors are sent to this email.
96              
97             =item B<-mail-err-level> - Error level for mails
98              
99             Set the minium error level that triggers an email. Level can be: note, warning, error
100              
101             =item B<-mail-subject> - Mail subject
102              
103             Optional mail subject line
104              
105             =item B<-mail-host> - Mail host
106              
107             Mailing host. If not specified uses 'localhost'
108              
109             =back
110              
111             =cut
112              
113             # Set of script-related default options
114             my @OPTIONS = (
115             ['mail-from=s', 'Mail sender', 'Email sender', ],
116             ['mail-to=s', 'Mail recipient', 'Email recipient(s). Where there are multiple recipients, they should be set as a comma seperated list of email addresses', ],
117             ['mail-error-to=s', 'Error mail recipient', 'Email recipient(s) for errors. If set, program errors are sent to this email.'],
118             ['mail-err-level=s', 'Error level for mails', 'Set the minium error level that triggers an email (must have error-to set). Level can be: note, warning, error', 'error'],
119             ['mail-subject=s', 'Mail subject', 'Optional mail subject line'],
120             ['mail-host=s', 'Mail host', 'Mailing host.', 'localhost'],
121             ) ;
122              
123              
124              
125             =head2 FIELDS
126              
127             The following fields should be defined either in the call to 'new()', as part of a 'set()' call, or called by their accessor method
128             (which is the same name as the field):
129              
130              
131             =over 4
132              
133              
134             =item B - Mail sender (required)
135              
136             Email sender
137              
138             =item B - Mail recipient(s) (required)
139              
140             Email recipient. Where there are multiple recipients, they should be set as a comma seperated list of email addresses
141              
142             =item B - Error mail recipient(s)
143              
144             Email recipient for errors. If set, program errors are sent to this email.
145              
146             =item B - Error level for mails
147              
148             Set the minium error level that triggers an email. Level can be: note, warning, error
149              
150             =item B - Mail subject
151              
152             Optional mail subject line
153              
154             =item B - Mail host
155              
156             Mailing host. If not specified uses 'localhost'
157              
158              
159             =back
160              
161             =cut
162              
163             my %FIELDS = (
164             'from' => '',
165             'to' => '',
166             'error_to' => '',
167             'err_level' => 'error',
168             'subject' => '',
169             'host' => 'localhost',
170            
171             ## Private
172             '_caught_error' => 0,
173             ) ;
174              
175             #============================================================================================
176              
177             =head2 CONSTRUCTOR
178              
179             =over 4
180              
181             =cut
182              
183             #============================================================================================
184              
185              
186             =item B< new([%args]) >
187              
188             Create a new Mail.
189              
190             The %args are specified as they would be in the B method (see L).
191              
192             =cut
193              
194             sub new
195             {
196 2     2 1 21 my ($obj, %args) = @_ ;
197              
198 2   33     19 my $class = ref($obj) || $obj ;
199              
200             # Create object
201 2         27 my $this = $class->SUPER::new(%args,
202             'requires' => [qw/Net::SMTP/],
203             'registered' => [qw/application_entry catch_error_entry/],
204             'feature_options' => \@OPTIONS,
205             ) ;
206            
207              
208             ## If associated with an app, set options
209 2         46 my $app = $this->app ;
210 2 50       6 if ($app)
211             {
212             ## Set options
213 2         8 $app->feature('Options')->append_options(\@OPTIONS) ;
214            
215             ## Update option defaults
216 2         6 $app->feature('Options')->defaults_from_obj($this, [keys %FIELDS]) ;
217             }
218              
219            
220 2         8 return($this) ;
221             }
222              
223              
224              
225             #============================================================================================
226              
227             =back
228              
229             =head2 CLASS METHODS
230              
231             =over 4
232              
233             =cut
234              
235             #============================================================================================
236              
237              
238             #-----------------------------------------------------------------------------
239              
240             =item B< init_class([%args]) >
241              
242             Initialises the Mail object class variables.
243              
244             =cut
245              
246             sub init_class
247             {
248 2     2 1 6 my $class = shift ;
249 2         7 my (%args) = @_ ;
250              
251             # Add extra fields
252 2         16 $class->add_fields(\%FIELDS, \%args) ;
253              
254             # init class
255 2         14 $class->SUPER::init_class(%args) ;
256              
257             }
258              
259             #============================================================================================
260              
261             =back
262              
263             =head2 OBJECT METHODS
264              
265             =over 4
266              
267             =cut
268              
269             #============================================================================================
270              
271              
272             #--------------------------------------------------------------------------------------------
273              
274             =item B< mail($content [, %args]) >
275              
276             Send some mail stored in $content. $content may either be a string (containing newlines), or an
277             ARRAY ref.
278              
279             Optionally %args may be specified (to set 'subject' etc).
280              
281             If no arguments are specified then just returns the mail object.
282              
283             =cut
284              
285             sub mail
286             {
287 12     12 1 14 my $this = shift ;
288 12         16 my ($content, %args) = @_ ;
289              
290 12 50       190 return $this unless $content ;
291            
292 0         0 $this->_dbg_prt(["mail() : content=\"$content\"\n"]) ;
293            
294 0         0 $this->set(%args) ;
295            
296 0         0 my $from = $this->from ;
297 0         0 my $mail_to = $this->to ;
298 0         0 my $subject = $this->subject ;
299 0         0 my $host = $this->host ;
300            
301            
302             ## error check
303 0 0       0 $this->throw_fatal("Mail: not specified 'from' field") unless $from ;
304 0 0       0 $this->throw_fatal("Mail: not specified 'to' field") unless $mail_to ;
305 0 0       0 $this->throw_fatal("Mail: not specified 'host' field") unless $host ;
306              
307 0         0 my @content ;
308 0 0       0 if (ref($content) eq 'ARRAY')
    0          
309             {
310 0         0 @content = @$content ;
311             }
312             elsif (!ref($content))
313             {
314 0         0 @content = split /\n/, $content ;
315             }
316              
317             ## For each recipient, need to send a separate mail
318 0         0 my @to = split /,/, $mail_to ;
319 0         0 foreach my $to (@to)
320             {
321 0         0 my $smtp = Net::SMTP->new($host); # connect to an SMTP server
322 0 0       0 $this->throw_fatal("Mail: unable to connect to '$host'") unless $smtp ;
323            
324 0         0 $smtp->mail($from); # use the sender's address here
325 0         0 $smtp->to($to); # recipient's address
326 0         0 $smtp->data(); # Start the mail
327            
328             # Send the header.
329 0         0 $smtp->datasend("To: $mail_to\n");
330 0         0 $smtp->datasend("From: $from\n");
331 0 0       0 $smtp->datasend("Subject: $subject\n") if $subject ;
332            
333             # Send the body.
334 0         0 $smtp->datasend("$_\n") foreach (@content) ;
335            
336 0         0 $smtp->dataend(); # Finish sending the mail
337 0         0 $smtp->quit; # Close the SMTP connection
338             }
339             }
340              
341             #----------------------------------------------------------------------------
342              
343             =item B< Mail([%args]) >
344              
345             Alias to L
346              
347             =cut
348              
349             *Mail = \&mail ;
350              
351              
352             #----------------------------------------------------------------------------
353              
354             =item B
355              
356             Called by the application framework at the start of the application.
357            
358             This method checks for the user specifying any of the options described above (see L) and handles
359             them if so.
360              
361             =cut
362              
363              
364             sub application_entry
365             {
366 1     1 1 4 my $this = shift ;
367              
368 1         20 $this->_dbg_prt(["application_entry()\n"], 2) ;
369              
370             ## Handle special options
371 1         22 my $app = $this->app ;
372 1         4 my %opts = $app->options() ;
373 1         7 $this->_dbg_prt(["mail options=",\%opts], 2) ;
374              
375              
376             ## Map from options to object data
377 1         3 foreach my $opt_entry_aref (@OPTIONS)
378             {
379 6         8 my $opt = $opt_entry_aref->[0] ;
380 6 50       12 if ($opts{$opt})
381             {
382 0           my $field = $opt ;
383 0           $field =~ s/[-]/_/g ;
384 0           $field =~ s/^mail\-// ;
385            
386 0           $this->set($field => $opts{$opt}) ;
387             }
388             }
389             }
390              
391              
392             #--------------------------------------------------------------------------------------------
393              
394             =item B< catch_error_entry($error) >
395              
396             Send some mail stored in $content. $content may either be a string (containing newlines), or an
397             ARRAY ref.
398              
399             Optionally %args may be specified (to set 'subject' etc)
400              
401             =cut
402              
403             sub catch_error_entry
404             {
405 0     0 1   my $this = shift ;
406 0           my ($error) = @_ ;
407              
408             ## skip if already inside an error
409 0 0         return if $this->_caught_error ;
410            
411 0           my $from = $this->from ;
412 0           my $error_to = $this->error_to ;
413 0           my $app = $this->app ;
414              
415 0           $this->_dbg_prt(["catch_error_entry() : from=$from error-to=$error_to app=$app\n"]) ;
416 0           $this->_dbg_prt(["error=", $error], 5) ;
417            
418             # skip if required fields not set
419 0 0 0       return unless $from && $error_to && $app ;
      0        
420              
421 0           my $appname = $app->name ;
422 0           my $level = $this->err_level ;
423            
424 0           $this->_dbg_prt(["mail level=$level, app=$appname\n"]) ;
425            
426             ## See if we mail it
427 0           my ($msg, $exitcode, $error_type) ;
428            
429             # If it's an error, mail it
430 0 0         if ($this->is_error($error))
431             {
432 0           ($msg, $exitcode) = $this->error_split($error) ;
433 0           $error_type = "fatal error" ;
434             }
435 0 0 0       if ($this->is_warning($error) && (($level eq 'warning') || ($level eq 'note')))
      0        
436             {
437 0           ($msg, $exitcode) = $this->error_split($error) ;
438 0           $error_type = "warning" ;
439             }
440 0 0 0       if ( $this->is_note($error) && ($level eq 'note') )
441             {
442 0           ($msg, $exitcode) = $this->error_split($error) ;
443 0           $error_type = "note" ;
444             }
445              
446 0           $this->_dbg_prt(["type=$error_type, exit=$exitcode, msg=\"$msg\"\n"]) ;
447              
448 0 0         if ($msg)
449             {
450 0           $this->_caught_error(1) ;
451 0           my $orig_to = $this->to ;
452 0           $this->mail(
453             $msg,
454             'to' => $error_to,
455             'subject' => "$appname $error_type",
456             ) ;
457 0           $this->to($orig_to) ;
458 0           $this->_caught_error(0) ;
459             }
460             }
461              
462             # ============================================================================================
463             # PRIVATE METHODS
464             # ============================================================================================
465              
466              
467             # ============================================================================================
468             # END OF PACKAGE
469              
470             =back
471              
472             =head1 DIAGNOSTICS
473              
474             Setting the debug flag to level 1 prints out (to STDOUT) some debug messages, setting it to level 2 prints out more verbose messages.
475              
476             =head1 AUTHOR
477              
478             Steve Price C<< >>
479              
480             =head1 BUGS
481              
482             None that I know of!
483              
484             =cut
485              
486             1;
487              
488             __END__