#37098: convert one-shot email notices to use message templates
[freeside.git] / httemplate / misc / email-customers.html
1 <%doc>
2
3 Allows emailing one or more customers, based on a search for customers.
4 Search can be specified either through cust_main fields as cgi params, or
5 through a base64 encoded frozen hash in the 'search' cgi param.  Form allows
6 selecting an existing msg_template, or creating a custom message, and shows a
7 preview of the message before sending.  If linked to as a popup, include the
8 cgi parameter 'popup' for proper header handling.
9
10 This may also be used as an element in other pages, enabling you to provide an
11 alternate initial form while using this for search freezing/thawing and 
12 preview/send actions, with the following options:
13
14 acl - the access right to use (defaults to 'Bulk send customer notices')
15
16 form_action - the URL to submit the form to
17
18 process_url - the URL for starting the JSRPC process
19
20 title - the title of the page
21
22 no_search_fields - arrayref of additional fields that are not search parameters
23
24 alternate_form - subroutine that returns alternate html for the initial form,
25 replaces msgnum/from/subject/body/action inputs and submit button, not
26 used if an action is specified
27
28 post_search_hook - sub hook for additional processing after search has been
29 processed from cgi, gets passed options 'conf' and 'search' (a reference to
30 the unfrozen %search hash), should be used to set msgnum or
31 from/subject/body cgi params
32
33 </%doc>
34 % if ($popup) {
35 <& /elements/header-popup.html, $title &>
36 % } else {
37 <& /elements/header.html, $title &>
38 % }
39
40 <& /elements/error.html &>
41
42 <FORM NAME="OneTrueForm" ACTION="<% $form_action %>" METHOD="POST">
43 <INPUT TYPE="hidden" NAME="table" VALUE="<% $table %>">
44 %# Mixing search params with from address, subject, etc. required special-case
45 %# handling of those, risked name conflicts, and caused massive problems with 
46 %# multi-valued search params.  We are no longer in search context, so we 
47 %# pack the search into a Storable string for later use.
48 <INPUT TYPE="hidden" NAME="search" VALUE="<% encode_base64(nfreeze(\%search)) %>">
49 <INPUT TYPE="hidden" NAME="popup" VALUE="<% $popup %>">
50 <INPUT TYPE="hidden" NAME="url" VALUE="<% $url | h %>">
51
52 % if ( $cgi->param('preview') ) {
53 %   # preview mode: at this point we have a msg_template (either "real" or
54 %   # draft) and $html_body and $text_body contain the preview message.
55 %   # give the user a chance to back out (by going back to edit mode).
56
57     <FONT SIZE="+2">Preview notice</FONT>
58     <& /elements/progress-init.html,
59                  'OneTrueForm',
60                  [ qw( search table msgnum ) ],
61                  $process_url,
62                  $pdest,
63     &>
64
65     <TABLE CLASS="fsinnerbox">
66     <INPUT TYPE="hidden" NAME="msgnum" VALUE="<% $msg_template->msgnum %>">
67 %   # kludge these through hidden inputs because they're not really part
68 %   # of the template, but should be sticky during draft editing
69     <INPUT TYPE="hidden" NAME="from_name" VALUE="<% $cgi->param('from_name') %>">
70     <INPUT TYPE="hidden" NAME="from_addr" VALUE="<% $cgi->param('from_addr') %>">
71
72 %   if ( !$msg_template->disabled ) {
73       <& /elements/tr-td-label.html, 'label' => 'Template:' &>
74         <td><% $msg_template->msgname |h %></td>
75       </tr>
76 %   }
77
78       <& /elements/tr-td-label.html, 'label' => 'From:' &>
79         <td><% $from |h %></td>
80       </tr>
81
82       <& /elements/tr-td-label.html, 'label' => 'Subject:' &>
83         <td><% $subject |h %></td>
84       </tr>
85
86       <TR><TD COLSPAN=2>&nbsp;</TD></TR>
87       <TR>
88         <TH ALIGN="right" VALIGN="top">Message (HTML display): </TD>
89         <TD CLASS="background" ALIGN="left"><% $html_body %></TD>
90       </TR>
91
92 %     my $text_body = HTML::FormatText->new(leftmargin=>0)->format(
93 %                       HTML::TreeBuilder->new_from_content(
94 %                         $html_body
95 %                       )
96 %                     );
97       <TR><TD COLSPAN=2>&nbsp;</TD></TR>
98       <TR>
99         <TH ALIGN="right" VALIGN="top">Message (Text display): </TD>
100         <TD CLASS="background" ALIGN="left">
101           <a href="javascript:void(0)" ID="email-message-text-view" style="color:#666666" onclick="showtext()">(view)</a>
102           <a href="javascript:void(0)" ID="email-message-text-hide" style="color:#666666; display: none;" onclick="hidetext()">(hide)</a>
103           <PRE id="email-message-text" style="display: none;"><% $text_body %></PRE>
104         </TD>
105       </TR>
106
107     </TABLE>
108
109     <SCRIPT>
110
111       function showtext() {
112         $('#email-message-text-view').css('display','none');
113         $('#email-message-text-hide').css('display','');
114         $('#email-message-text').slideDown();
115       }
116
117       function hidetext() {
118         $('#email-message-text-view').css('display','');
119         $('#email-message-text-hide').css('display','none');
120         $('#email-message-text').slideUp();
121       }
122
123       function areyousure(href) {
124         if (confirm("Send this notice to <% ($num_cust > 1) ? "$num_cust customers" : '1 customer' %> ?")) {
125           process();
126         }
127       }
128     </SCRIPT>
129
130     <BR>
131     <INPUT TYPE="submit" NAME="edit" VALUE="Edit">
132     <INPUT TYPE="button" VALUE="Send notice" onClick="areyousure()">
133
134 % } elsif ($opt{'alternate_form'}) {
135
136 <% &{$opt{'alternate_form'}}() %>
137
138 % } else {
139 %   # Edit mode.
140
141 <SCRIPT TYPE="text/javascript">
142 function toggle(obj) {
143   document.getElementById('table_no_template').style.display = (obj.value == 0) ? '' : 'none';
144 }
145
146 </SCRIPT>
147 % if ( $msg_template and $msg_template->disabled ) {
148 %   # if we've already established a draft template, don't let msgnum be changed
149     <& /elements/hidden.html,
150       field => 'msgnum',
151       curr_value => ($cgi->param('msgnum') || ''),
152     &>
153 % } else {
154 Template: 
155     <& /elements/select-msg_template.html,
156         onchange   => 'toggle(this)',
157         curr_value => ($cgi->param('msgnum') || ''),
158     &>
159     <BR>
160 % }
161   <TABLE BGCOLOR="#cccccc" CELLSPACING=0 WIDTH="100%" id="table_no_template">
162     <& /elements/tr-td-label.html, 'label' => 'From:' &>
163       <TD><& /elements/input-text.html,
164               'field' => 'from_name',
165               'value' => $conf->config('invoice_from_name', $agent_virt_agentnum) ||
166                          $conf->config('company_name', $agent_virt_agentnum), #?
167               'size'  => 20,
168               'curr_value' => $cgi->param('from_name'),
169           &>&nbsp;&lt;\
170           <& /elements/input-text.html,
171               'field' => 'from_addr',
172               'type'  => 'email', # HTML5, woot
173               'value' => $conf->config('invoice_from', $agent_virt_agentnum),
174               'size'  => 20,
175               'curr_value' => $cgi->param('from_addr'),
176           &>&gt;</TD>
177  
178     <& /elements/tr-input-text.html,
179                  'field' => 'subject',
180                  'label' => 'Subject:',
181                  'size'  => 50,
182                  'curr_value' => $subject,
183     &>
184
185     <TR>
186       <TD ALIGN="right" VALIGN="top" STYLE="padding-top:3px">Message: </TD>
187       <TD><& /elements/htmlarea.html, 
188                'field' => 'body',
189                'width' => 763,
190                'curr_value' => $body,
191           &>
192       </TD>
193     </TR>
194
195   </TABLE>
196
197   <INPUT TYPE="submit" NAME="preview" VALUE="Preview notice">
198
199 % } #end not action or alternate form
200
201 </FORM>
202
203 <& /elements/footer.html &>
204
205 <%init>
206
207 my %opt = @_;
208
209 $opt{'acl'} ||= 'Bulk send customer notices';
210
211 die "access denied"
212   unless $FS::CurrentUser::CurrentUser->access_right($opt{'acl'});
213
214 my $conf = FS::Conf->new;
215 my @no_search_fields = qw( table from subject html_body text_body popup url );
216
217 my $form_action = $opt{'form_action'} || 'email-customers.html';
218 my $process_url = $opt{'process_url'} || 'process/email-customers.html';
219 my $title = $opt{'title'} || 'Send customer notices';
220 push( @no_search_fields, @{$opt{'no_search_fields'}} ) if $opt{'no_search_fields'};
221
222 my $table = $cgi->param('table') or die "'table' required";
223 my $agent_virt_agentnum = $cgi->param('agent_virt_agentnum') || '';
224
225 my $popup = $cgi->param('popup');
226 my $url   = $cgi->param('url');
227 my $pdest = { 'message' => "Notice sent" };
228 $pdest->{'url'} = $cgi->param('url') if $url;
229
230 my %search;
231 if ( $cgi->param('search') ) {
232   %search = %{ thaw(decode_base64( $cgi->param('search') )) };
233 }
234 else {
235   %search = $cgi->Vars;
236   delete $search{$_} for @no_search_fields;
237   # FS::$table->search is expected to know which parameters might be 
238   # multi-valued, and to accept scalar values for them also.  No good 
239   # solution to this since CGI can't tell whether a parameter _might_
240   # have had multiple values, only whether it does.
241   @search{keys %search} = map { /\0/ ? [ split /\0/, $_ ] : $_ } values %search;
242 }
243
244 &{$opt{'post_search_hook'}}(
245   'conf'   => $conf,
246   'search' => \%search,
247 ) if $opt{'post_search_hook'};
248
249 my $num_cust;
250 my $from = '';
251 if ( $cgi->param('from') ) {
252   $from = $cgi->param('from');
253 } elsif ( $cgi->param('from_name') ) {
254   $from = ($cgi->param('from_name') . ' <' . $cgi->param('from_addr') . '>');
255 } elsif ( $cgi->param('from_addr') ) {
256   $from = $cgi->param('from_addr');
257 }
258
259 my $msg_template = '';
260 if ( $cgi->param('msgnum') =~ /^(\d+)$/ ) {
261   $msg_template = FS::msg_template->by_key($1)
262     or die "template not found: ".$cgi->param('msgnum');
263 }
264
265 my $subject = $cgi->param('subject');
266 my $body = $cgi->param('body');
267 my ($html_body, $text_body);
268
269 if ( !$cgi->param('preview') ) {
270
271   # edit mode: initialize the fields from the saved draft, if there is one
272   if ( $msg_template and $msg_template->disabled eq 'D' ) {
273     my $content = $msg_template->content(''); # no localization on these yet
274     $subject ||= $content->subject;
275     $body ||= $content->body;
276   }
277
278 } else {
279
280   my $sql_query = "FS::$table"->search(\%search);
281   my $count_query = delete($sql_query->{'count_query'});
282   my $count_sth = dbh->prepare($count_query)
283     or die "Error preparing $count_query: ". dbh->errstr;
284   $count_sth->execute
285     or die "Error executing $count_query: ". $count_sth->errstr;
286   my $count_arrayref = $count_sth->fetchrow_arrayref;
287   $num_cust = $count_arrayref->[0];
288
289   if ( !$msg_template or $msg_template->disabled eq 'D' ) {
290     # then this is a one-off template; edit it in place
291     my $subject = $cgi->param('subject') || '';
292     my $body = $cgi->param('body') || '';
293
294     # create a draft template
295     $msg_template ||= FS::msg_template->new({
296       msgclass  => 'email',
297       disabled  => 'D',
298     });
299     # anyone have a better idea for msgname?
300     $msg_template->set('msgname' => "Notice " . DateTime->now->iso8601);
301     $msg_template->set('from_addr' => $from);
302     my %content = (
303       subject => $subject,
304       body    => $body,
305     );
306     my $error;
307     if ( $msg_template->msgnum ) {
308       $error = $msg_template->replace(%content);
309     } else {
310       $error = $msg_template->insert(%content);
311     }
312
313     if ( $error ) {
314       $cgi->param('error', $error);
315       $cgi->delete('preview'); # don't go on to preview stage yet
316       undef $msg_template;
317     }
318   }
319   # unless creating the msg_template failed, we now have one, so construct a
320   # preview message from the first customer/whatever in the search results
321
322   if ( $msg_template ) { 
323     $sql_query->{'extra_sql'} .= ' LIMIT 1';
324     $sql_query->{'select'} = "$table.*";
325     $sql_query->{'order_by'} = '';
326     my $object = qsearchs($sql_query);
327     my $cust = $object->cust_main;
328     my %msgopts = (
329       'cust_main' => $cust,
330       'object' => $object,
331     );
332
333     my $cust_msg = $msg_template->prepare(%msgopts);
334     $from = $cust_msg->env_from;
335     $html_body = $cust_msg->preview;
336     if ( $cust_msg->header =~ /^subject: (.*)/mi ) {
337       $subject = $1;
338     }
339   }
340 }
341
342 </%init>