File Coverage

blib/lib/CGI/FormBuilder/Mail/FormatMultiPart.pm
Criterion Covered Total %
statement 31 222 13.9
branch 9 72 12.5
condition 8 42 19.0
subroutine 8 18 44.4
pod 0 2 0.0
total 56 356 15.7


line stmt bran cond sub pod time code
1             package CGI::FormBuilder::Mail::FormatMultiPart;
2              
3 1     1   32498 use strict;
  1         4  
  1         44  
4 1     1   6 use warnings;
  1         2  
  1         48  
5              
6             our $VERSION = 1.000006;
7              
8 1     1   18 use English '-no_match_vars;';
  1         2  
  1         9  
9              
10 1     1   659 use CGI::FormBuilder::Util;
  1         2  
  1         150  
11 1     1   5 use CGI::FormBuilder::Field;
  1         2  
  1         76  
12              
13             require MIME::Types; # for MIME::Lite's AUTO type detection - required
14             require MIME::Lite;
15             require Text::FormatTable;
16             require HTML::QuickTable;
17              
18 1     1   1060 use Regexp::Common qw( net );
  1         3088  
  1         7  
19              
20             sub new {
21 13     13 0 51638 my $class = shift;
22 13         52 my $self = { @_ };
23              
24 13         47 bless $self, $class;
25 13         38 return $self;
26             }
27              
28             sub mailresults {
29 11     11 0 110 my ($self) = @_;
30              
31             # slice args into nicer var names, checking some params
32              
33 11         28 my $form = $self->{form};
34 11 100 66     94 puke "No CGI::FormBuilder passed as form arg"
35             if !$form || !$form->isa('CGI::FormBuilder');
36              
37 10         35 my ($subject, $to, $cc, $bcc, $from, $smtp )
38 10         16 = @{$self}{qw( subject to cc bcc from smtp )};
39              
40 60 100       232 puke "Address/subject args should all be scalars"
41 10 100       20 if scalar grep { defined && ref }
42             ( $to, $from, $smtp, $cc, $bcc, $subject );
43              
44 4 100 66     25 puke "Cannot send mail without to, from, and smtp args"
      66        
45             if !$to || !$from || !$smtp;
46            
47 1 50 33     53 puke "arg 'smtp' in bad format"
      33        
48             if !( $smtp eq 'localhost'
49             || $smtp =~ m{ \A $RE{net}{domain}{-nospace} \z }xms
50             || $smtp =~ m{ \A $RE{net}{IPv4} \z }xms
51             )
52             ;
53              
54             # let MIME::Lite check e-mail address or address list format
55             # (VALIDATE pattern for multiple addresses too much of a pain)
56              
57 0           my ($format, $skipfields, $skip ) = @{$self}{qw( format skipfields skip )};
  0            
58              
59 0 0         $format = $self->{format} = 'plain' if !$format;
60 0 0 0       if ( ref $format
  0            
61             || !grep { $_ eq $format } qw( plain html both )
62             ) {
63 0           puke "Arg format should be 'plain', 'html', or 'both'.";
64             }
65              
66 0 0 0       if ($skip || (defined $skipfields && ref $skipfields ne 'ARRAY')) {
      0        
67 0           belch __PACKAGE__
68             ." prefers arg 'skipfields' as arrayref, not skipping any";
69 0           $skipfields = $self->{skipfields} = [ ];
70             }
71              
72             # what's the default subject if not found?
73 0 0         if (!$subject) {
74 0   0       $self->{subject} = $subject
75             ||= sprintf $form->{messages}->mail_results_subject, $form->title;
76             }
77              
78             # set up a hash of the individual CGI::FormBuilder::Field objects and
79             # put it into $self to pass around. it's useful.
80 0           my $fbflds = $self->{_fbflds} = { map { ( "$_" => $_ ) } $form->field };
  0            
81              
82             # ok, now set up the e-mail
83              
84 0           my $parts = 1; # count parts to determine message type, args, construction
85 0 0         $parts++ if $format eq 'both';
86              
87 0           my @file_attachments = $self->_file_attachments();
88             #debug 1, "file_attachments: '@file_attachments'";
89              
90 0           $parts += scalar @file_attachments;
91              
92             #debug 1, "parts: '$parts'";
93              
94 0           my $msg = undef;
95 0 0         my %msg_args = (
96             From => $from,
97             To => $to,
98             Subject => $subject,
99             Type => ($parts > 1) ? 'multipart/mixed' : "text/$format",
100             );
101 0 0         $msg_args{Cc} = $cc if defined $cc;
102 0 0         $msg_args{Bcc} = $bcc if defined $bcc;
103              
104 0 0         if ($parts == 1) {
105 0           $msg_args{Data} = $self->_format_text();
106 0           $msg = MIME::Lite->new( %msg_args );
107             }
108             else {
109 0           $msg = MIME::Lite->new( %msg_args );
110 0 0 0       if ($format eq 'plain' || $format eq 'both') {
111 0           $msg->attach(
112             Type => 'TEXT',
113             Data => $self->_format_text_plain(),
114             );
115             }
116 0 0 0       if ($format eq 'html' || $format eq 'both') {
117 0           $msg->attach(
118             Type => 'text/html',
119             Data => $self->_format_text_html(),
120             );
121             }
122              
123 0           $msg->attach( %{$_} ) for @file_attachments;
  0            
124             }
125              
126 0           my $success = eval $msg->send_by_smtp( $smtp );
127              
128 0 0 0       if ($EVAL_ERROR || !$success) {
129 0           puke("Could not send mail. $EVAL_ERROR");
130             }
131              
132 0           return;
133             }
134              
135             sub _format_text {
136             # a simple dispatch
137 0     0     my ($self) = @_;
138 0 0         return ($self->{format} eq 'html')
139             ? $self->_format_text_html()
140             : $self->_format_text_plain();
141             }
142              
143             sub _format_text_plain {
144 0     0     my ($self) = @_;
145              
146 0           my $text = $self->{subject}."\n\nForm Data:\n\n";
147              
148 0           my $fmt = '| l | l |';
149              
150 0           my $table_data = Text::FormatTable->new($fmt);
151 0           my $data_form = $self->_data_form();
152 0           $table_data->rule;
153 0           $table_data->head( @{ $data_form->[0] } );
  0            
154 0           $table_data->rule;
155              
156             # not sure yet if it's better to have rules between each var
157 0           do { $table_data->row( @{$_} ); $table_data->rule; }
  0            
  0            
  0            
158 0           for @{$data_form}[ 1 .. $#{$data_form} ];
  0            
159              
160             #$table_data->row( @{$_} ) for @{$data_form}[ 1 .. $#{$data_form} ];
161             #$table_data->rule;
162              
163 0           $text .= $table_data->render(72);
164              
165 0           my $data_files = $self->_data_files();
166 0 0         if (defined $data_files) {
167 0           $text .= "\n\nUploaded Files:\n\n";
168 0           my $table_files = Text::FormatTable->new($fmt);
169 0           $table_files->rule;
170 0           $table_files->head( @{ $data_files->[0] } );
  0            
171 0           $table_files->rule;
172 0           $table_files->row( @{$_} ) for @{$data_files}[ 1 .. $#{$data_files} ];
  0            
  0            
  0            
173 0           $table_files->rule;
174 0           $text .= $table_files->render(72);
175             }
176              
177 0           my $data_env = $self->_data_env();
178 0 0         if (defined $data_env) {
179 0           $text .= "\n\nBrowser/Connect Info:\n\n";
180 0           my $table_env = Text::FormatTable->new($fmt);
181 0           $table_env->rule;
182 0           $table_env->head( @{ $data_env->[0] } );
  0            
183 0           $table_env->rule;
184 0           $table_env->row( @{$_} ) for @{$data_env}[ 1 .. $#{$data_env} ];
  0            
  0            
  0            
185 0           $table_env->rule;
186 0           $text .= $table_env->render(72);
187             }
188              
189 0           $text .= "\n\nTime:\n\n";
190 0           my $table_time = Text::FormatTable->new($fmt);
191 0           my $data_time = $self->_data_time();
192 0           $table_time->rule;
193 0           $table_time->head( @{ $data_time->[0] } );
  0            
194 0           $table_time->rule;
195 0           $table_time->row( @{$_} ) for @{$data_time}[ 1 .. $#{$data_time} ];
  0            
  0            
  0            
196 0           $table_time->rule;
197 0           $text .= $table_time->render(72);
198              
199             #print "
\n$text\n
\n";
200              
201 0           return $text;
202             }
203              
204             sub _format_text_html {
205 0     0     my ($self) = @_;
206              
207 0           my ($form, $fbflds, $skipfields, $subject, $css)
208 0           = @{$self}{qw( form _fbflds skipfields subject css )};
209              
210 0           my $fmt = $self->{html_qt_format};
211 0 0 0       if ($fmt && ref $fmt ne 'HASH') {
212 0           belch "html_qt_format is hashref for HTML::QuickTable. default used.";
213 0           undef $fmt;
214             }
215 0 0         $fmt = { } if !$fmt;
216              
217 0           my $qt_real_fmt = {
218             # all the defaults:
219             cellspacing => 0,
220             cellpadding => 0,
221             border => 0,
222             labels => 1,
223             stylesheet => 1,
224             styleclass => 'fb_mail',
225             useid => 'fb_mail',
226            
227             # or override with specified values:
228 0           %{$fmt},
229              
230             # except no header, we never want to allow that:
231             header => 0,
232             };
233              
234 0           my $base_css_class = $qt_real_fmt->{styleclass};
235 0           my $default_css = <
236             *.fb_mail {
237             font-family: Arial, Helvetica, sans-serif;
238             }
239             TABLE.fb_mail {
240             padding: 0.5em;
241             border: 1px solid black;
242             }
243             TD.fb_mail {
244             font-face: Arial, Helvetica, sans-serif;
245             text-align: left;
246             vertical-align: top;
247             padding: 0.5em;
248             background: white;
249             border: 1px dotted orange;
250             }
251             H1.fb_mail {
252             color: red;
253             }
254             END_CSS
255              
256             # their styles will override defaults, but defaults will still be used
257             # if they don't make a full CSS spec for the table:
258 0 0         $css = '' if !defined $css;
259 0           $css = $default_css."\n".$css;
260              
261             # OK, the only question is how to format CSS for e-mail so it will work.
262 0           my $html = <
263            
264              
265            
266            
267              
268            
271              
272            
273            
274              
275            

$subject

276              
277             END_HTML
278              
279 0           my $qt = HTML::QuickTable->new(
280 0           %{$qt_real_fmt},
281             );
282              
283 0           $html .= qq{

Form Data:

\n};
284              
285             # clone form data so it's still ok for plain when we tweak it for html here
286 0           my @data_form = @{ $self->_data_form() };
  0            
287 0           $_->[1] =~ s{ \n }{
\n}xms for @data_form; # turn \n to
288 0           $html .= $qt->render( \@data_form );
289              
290 0           my $data_files = $self->_data_files();
291 0 0         if (defined $data_files) {
292 0           $html .= qq{

Uploaded Files:

\n};
293 0           $html .= $qt->render( $data_files );
294             }
295              
296 0           my $data_env = $self->_data_env();
297 0 0         if (defined $data_env) {
298 0           $html .= qq{

Browser/Connect Info:

\n};
299 0           $html .= $qt->render( $data_env );
300             }
301              
302 0           $html .= qq{

Time:

\n};
303              
304 0           $html .= $qt->render( $self->_data_time() );
305              
306             #print $html;
307              
308 0           return $html;
309             }
310              
311             sub _skipfields_lookup {
312 0     0     my ($self) = @_;
313              
314 0 0         return $self->{_skipfields_lookup} if exists $self->{_skipfields_lookup};
315              
316 0           my $skipfields_lookup
317             = $self->{_skipfields_lookup}
318 0           = { map { ( $_ => 1 ) } @{ $self->{skipfields} } };
  0            
319            
320 0           return $skipfields_lookup;
321             }
322              
323             sub _file_field_names {
324 0     0     my ($self) = @_;
325              
326 0           my $fbflds = $self->{_fbflds};
327              
328 0           my $skipfields_lookup = $self->_skipfields_lookup;
329              
330             return (
331 0           grep { !exists $skipfields_lookup->{$_} } # is not skipped
  0            
332 0           grep { $fbflds->{$_}->type eq 'file' } # type is file
333             $self->{form}->fields # in order of fields
334             );
335             }
336              
337             sub _file_attachments {
338 0     0     my ($self) = @_;
339              
340 0           my ($form, $fbflds) = @{$self}{qw( form _fbflds )};
  0            
341              
342             return ( # return array of hashrefs suitable for MIME::Type attachments
343 0           map { { Type => 'AUTO',
  0            
344             FH => $form->field($_),
345             Filename => $form->field($_),
346             Id => $_,
347             Disposition => 'attachment',
348             }
349             }
350 0           grep { $fbflds->{$_}->value } # only files actually uploaded
351             $self->_file_field_names()
352             );
353             }
354              
355             sub _data_form {
356 0     0     my ($self) = @_;
357              
358             # might be called twice in 'both', so no need to re-generate
359 0 0         return $self->{_data_form} if exists $self->{_data_form};
360              
361 0           my ($form, $fbflds) = @{$self}{qw( form _fbflds )};
  0            
362              
363 0           my $skipfields_lookup = $self->_skipfields_lookup;
364              
365 0           my $data = [
366             [ 'Field' => 'Value' ]
367             ];
368              
369 0           my @field_names = $form->fields;
370              
371              
372             FIELD:
373 0           foreach my $name ( @field_names ) {
374 0 0         next FIELD if exists $skipfields_lookup->{$name};
375 0 0         next FIELD if $fbflds->{$name}->type eq 'file';
376 0           my @values = $form->field($name);
377 0           my $value = join("\n",@values);
378 0 0         $value = ' ' if !$value;
379 0           push @{$data}, [ "$name" => $value ];
  0            
380             }
381              
382             # cache in self
383 0           $self->{_data_form} = $data;
384 0           return $data;
385             }
386              
387             sub _data_files {
388 0     0     my ($self) = @_;
389              
390 0 0         return $self->{_data_files} if exists $self->{_data_files};
391              
392 0           my ($form) = @{$self}{qw( form )};
  0            
393              
394 0           my $data = undef;
395              
396 0           my @file_field_names = $self->_file_field_names();
397              
398 0 0         if (scalar @file_field_names) {
399 0           $data = [
400             [ 'Field' => 'Attachment Status', ],
401             ];
402 0           foreach my $name (@file_field_names) {
403 0           my $value = $form->field($name);
404 0 0         $value = 'Not Uploaded' if !defined $value;
405 0           push @{$data}, [ "$name" => "attached as $value" ];
  0            
406             }
407             }
408 0           $self->{_data_files} = $data;
409              
410 0           return $data;
411             }
412              
413             sub _data_env {
414 0     0     my ($self) = @_;
415 0 0         return $self->{_data_env} if exists $self->{_data_env};
416              
417 0           my $data = undef;
418 0 0         if (scalar(keys %ENV)) {
419 0           $data = [
420             [ 'Item', 'Value' ],
421 0   0       ( map { [ $_ => $ENV{$_} ] }
422             grep exists $ENV{$_} && defined $ENV{$_},
423             qw( HTTP_USER_AGENT HTTP_REFERER REMOTE_ADDR REQUEST_URI )
424             ),
425             ];
426             }
427 0           $self->{_data_env} = $data;
428            
429 0           return $data;
430             }
431              
432             sub _data_time {
433 0     0     my ($self) = @_;
434 0 0         return $self->{_data_time} if $self->{_data_time};
435              
436 0           my $data = [
437             [ 'Time Zone' => 'Time' ],
438             [ 'Local System' => scalar localtime ],
439             [ 'Greenwich Mean' => scalar gmtime ],
440             ];
441              
442 0           $self->{_data_time} = $data;
443              
444 0           return $data;
445             }
446              
447             1;
448              
449             __END__