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.
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:
14 acl - the access right to use (defaults to 'Bulk send customer notices')
16 form_action - the URL to submit the form to
18 process_url - the URL for starting the JSRPC process
20 title - the title of the page
22 no_search_fields - arrayref of additional fields that are not search parameters
24 alternate_form - subroutine that returns alternate html for the initial form,
25 replaces msgnum/from/subject/html_body/action inputs and submit button, not
26 used if an action is specified
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/html_body cgi params
35 <& /elements/header-popup.html, $title &>
37 <& /elements/header.html, $title &>
41 <FORM NAME="OneTrueForm" ACTION="<% $form_action %>" METHOD="POST">
42 <INPUT TYPE="hidden" NAME="table" VALUE="<% $table %>">
43 %# Mixing search params with from address, subject, etc. required special-case
44 %# handling of those, risked name conflicts, and caused massive problems with
45 %# multi-valued search params. We are no longer in search context, so we
46 %# pack the search into a Storable string for later use.
47 <INPUT TYPE="hidden" NAME="search" VALUE="<% encode_base64(nfreeze(\%search)) %>">
48 <INPUT TYPE="hidden" NAME="popup" VALUE="<% $popup %>">
49 <INPUT TYPE="hidden" NAME="url" VALUE="<% $url | h %>">
51 % if ( $cgi->param('action') eq 'send' ) {
53 <FONT SIZE="+2">Sending notice</FONT>
55 <& /elements/progress-init.html,
57 [ qw( search table from subject html_body text_body msgnum ) ],
62 % } elsif ( $cgi->param('action') eq 'preview' ) {
64 <FONT SIZE="+2">Preview notice</FONT>
68 % if ( $cgi->param('action') ) {
70 <TABLE CLASS="fsinnerbox">
71 <INPUT TYPE="hidden" NAME="msgnum" VALUE="<% scalar($cgi->param('msgnum')) %>">
73 % if ( $msg_template ) {
74 <& /elements/tr-fixed.html,
75 'label' => 'Template:',
76 'value' => $msg_template->msgname,
80 <& /elements/tr-fixed.html,
86 <& /elements/tr-fixed.html,
88 'label' => 'Subject:',
92 <INPUT TYPE="hidden" NAME="html_body" VALUE="<% $html_body |h %>">
93 <TR><TD COLSPAN=2> </TD></TR>
95 <TH ALIGN="right" VALIGN="top">Message (HTML display): </TD>
96 <TD CLASS="background" ALIGN="left"><% $html_body %></TD>
99 % my $text_body = HTML::FormatText->new(leftmargin=>0)->format(
100 % HTML::TreeBuilder->new_from_content(
104 <INPUT TYPE="hidden" NAME="text_body" VALUE="<% $text_body |h %>">
105 <TR><TD COLSPAN=2> </TD></TR>
107 <TH ALIGN="right" VALIGN="top">Message (Text display): </TD>
108 <TD CLASS="background" ALIGN="left">
109 <a href="javascript:void(0)" ID="email-message-text-view" style="color:#666666" onclick="showtext()">(view)</a>
110 <a href="javascript:void(0)" ID="email-message-text-hide" style="color:#666666; display: none;" onclick="hidetext()">(hide)</a>
111 <PRE id="email-message-text" style="display: none;"><% $text_body %></PRE>
117 % if ( $cgi->param('action') eq 'preview' ) {
121 function showtext() {
122 $('#email-message-text-view').css('display','none');
123 $('#email-message-text-hide').css('display','');
124 $('#email-message-text').slideDown();
127 function hidetext() {
128 $('#email-message-text-view').css('display','');
129 $('#email-message-text-hide').css('display','none');
130 $('#email-message-text').slideUp();
133 function areyousure(href) {
134 return confirm("Send this notice to <% ($num_cust > 1) ? "$num_cust customers" : '1 customer' %> ?");
139 <INPUT TYPE="hidden" NAME="action" VALUE="send">
140 <INPUT TYPE="submit" VALUE="Send notice" onClick="return areyousure()">
144 % } elsif ($opt{'alternate_form'}) {
146 <% &{$opt{'alternate_form'}}() %>
150 <SCRIPT TYPE="text/javascript">
151 function toggle(obj) {
152 document.getElementById('table_no_template').style.display = (obj.value == 0) ? '' : 'none';
157 <& /elements/select-msg_template.html,
158 onchange => 'toggle(this)',
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), #?
169 <& /elements/input-text.html,
170 'field' => 'from_addr',
171 'type' => 'email', # HTML5, woot
172 'value' => $conf->config('invoice_from', $agent_virt_agentnum),
176 <& /elements/tr-input-text.html,
177 'field' => 'subject',
178 'label' => 'Subject:',
183 <TD ALIGN="right" VALIGN="top" STYLE="padding-top:3px">Message: </TD>
184 <TD><& /elements/htmlarea.html,
185 'field' => 'html_body',
195 <INPUT TYPE="hidden" NAME="action" VALUE="preview">
196 <INPUT TYPE="submit" VALUE="Preview notice">
198 % } #end not action or alternate form
202 % if ( $cgi->param('action') eq 'send' ) {
203 <SCRIPT TYPE="text/javascript">
208 <& /elements/footer.html &>
214 $opt{'acl'} ||= 'Bulk send customer notices';
217 unless $FS::CurrentUser::CurrentUser->access_right($opt{'acl'});
219 my $conf = FS::Conf->new;
220 my @no_search_fields = qw( action table from subject html_body text_body popup url );
222 my $form_action = $opt{'form_action'} || 'email-customers.html';
223 my $process_url = $opt{'process_url'} || 'process/email-customers.html';
224 my $title = $opt{'title'} || 'Send customer notices';
225 push( @no_search_fields, @{$opt{'no_search_fields'}} ) if $opt{'no_search_fields'};
227 my $table = $cgi->param('table') or die "'table' required";
228 my $agent_virt_agentnum = $cgi->param('agent_virt_agentnum') || '';
230 my $popup = $cgi->param('popup');
231 my $url = $cgi->param('url');
232 my $pdest = { 'message' => "Notice sent" };
233 $pdest->{'url'} = $cgi->param('url') if $url;
236 if ( $cgi->param('search') ) {
237 %search = %{ thaw(decode_base64( $cgi->param('search') )) };
240 %search = $cgi->Vars;
241 delete $search{$_} for @no_search_fields;
242 # FS::$table->search is expected to know which parameters might be
243 # multi-valued, and to accept scalar values for them also. No good
244 # solution to this since CGI can't tell whether a parameter _might_
245 # have had multiple values, only whether it does.
246 @search{keys %search} = map { /\0/ ? [ split /\0/, $_ ] : $_ } values %search;
249 &{$opt{'post_search_hook'}}(
251 'search' => \%search,
252 ) if $opt{'post_search_hook'};
256 if ( $cgi->param('from') ) {
257 $from = $cgi->param('from');
258 } elsif ( $cgi->param('from_name') ) {
259 $from = ($cgi->param('from_name') . ' <' . $cgi->param('from_addr') . '>');
260 } elsif ( $cgi->param('from_addr') ) {
261 $from = $cgi->param('from_addr');
264 my $subject = $cgi->param('subject') || '';
265 my $html_body = $cgi->param('html_body') || '';
267 my $msg_template = '';
269 if ( $cgi->param('action') eq 'preview' ) {
271 my $sql_query = "FS::$table"->search(\%search);
272 my $count_query = delete($sql_query->{'count_query'});
273 my $count_sth = dbh->prepare($count_query)
274 or die "Error preparing $count_query: ". dbh->errstr;
276 or die "Error executing $count_query: ". $count_sth->errstr;
277 my $count_arrayref = $count_sth->fetchrow_arrayref;
278 $num_cust = $count_arrayref->[0];
280 if ( $cgi->param('msgnum') ) {
281 $msg_template = qsearchs('msg_template',
282 { msgnum => scalar($cgi->param('msgnum')) } )
283 or die "template not found: ".$cgi->param('msgnum');
284 $sql_query->{'extra_sql'} .= ' LIMIT 1';
285 $sql_query->{'select'} = "$table.*";
286 $sql_query->{'order_by'} = '';
287 my $object = qsearchs($sql_query);
288 my $cust = $object->cust_main;
290 'cust_main' => $cust,
294 my $cust_msg = $msg_template->prepare(%msgopts);
295 $from = $cust_msg->env_from;
296 $html_body = $cust_msg->preview;
297 if ( $cust_msg->header =~ /^subject: (.*)/mi ) {