1 package FS::msg_template;
4 use base qw( FS::Record );
6 use FS::Misc qw( generate_email send_email );
8 use FS::Record qw( qsearch qsearchs );
10 use Date::Format qw( time2str );
11 use HTML::Entities qw( encode_entities) ;
18 FS::msg_template - Object methods for msg_template records
24 $record = new FS::msg_template \%hash;
25 $record = new FS::msg_template { 'column' => 'value' };
27 $error = $record->insert;
29 $error = $new_record->replace($old_record);
31 $error = $record->delete;
33 $error = $record->check;
37 An FS::msg_template object represents a customer message template.
38 FS::msg_template inherits from FS::Record. The following fields are currently
53 Agent associated with this template. Can be NULL for a global template.
57 MIME type. Defaults to text/html.
65 The message subject line, in L<Text::Template> format.
69 The message body, as plain text or HTML, in L<Text::Template> format.
83 Creates a new template. To add the template to the database, see L<"insert">.
85 Note that this stores the hash reference, not a distinct copy of the hash it
86 points to. You can ask the object for a copy with the I<hash> method.
90 # the new method can be inherited from FS::Record, if a table method is defined
92 sub table { 'msg_template'; }
96 Adds this record to the database. If there is an error, returns the error,
97 otherwise returns false.
101 # the insert method can be inherited from FS::Record
105 Delete this record from the database.
109 # the delete method can be inherited from FS::Record
111 =item replace OLD_RECORD
113 Replaces the OLD_RECORD with this one in the database. If there is an error,
114 returns the error, otherwise returns false.
118 # the replace method can be inherited from FS::Record
122 Checks all fields to make sure this is a valid template. If there is
123 an error, returns the error, otherwise returns false. Called by the insert
128 # the check method should currently be supplied - FS::Record contains some
129 # data checking routines
135 $self->ut_numbern('msgnum')
136 || $self->ut_text('msgname')
137 || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
138 || $self->ut_textn('mime_type')
139 || $self->ut_anything('subject')
140 || $self->ut_anything('body')
141 || $self->ut_enum('disabled', [ '', 'Y' ] )
142 || $self->ut_textn('from_addr')
144 return $error if $error;
146 my $body = $self->body;
147 $body =~ s/ / /g; # just in case these somehow get in
150 $self->mime_type('text/html') unless $self->mime_type;
155 =item prepare OPTION => VALUE
157 Fills in the template and returns a hash of the 'from' address, 'to'
158 addresses, subject line, and body.
160 Options are passed as a list of name/value pairs:
166 Customer object (required).
170 Additional context object (currently, can be a cust_main object, cust_pkg
171 object, or cust_bill object).
178 my( $self, %opt ) = @_;
180 my $cust_main = $opt{'cust_main'};
181 my $object = $opt{'object'};
182 warn "preparing template '".$self->msgname."' to cust#".$cust_main->custnum."\n"
185 my $subs = $self->substitutions;
188 # create substitution table
191 foreach my $obj ($cust_main, $object || ()) {
192 foreach my $name (@{ $subs->{$obj->table} }) {
195 $hash{$name} = $obj->$name();
197 elsif( ref($name) eq 'ARRAY' ) {
198 # [ foo => sub { ... } ]
199 $hash{$name->[0]} = $name->[1]->($obj);
202 warn "bad msg_template substitution: '$name'\n";
207 $_ = encode_entities($_) foreach values(%hash); # HTML escape
213 my $subject_tmpl = new Text::Template (
215 SOURCE => $self->subject,
217 my $subject = $subject_tmpl->fill_in( HASH => \%hash );
219 my $body_tmpl = new Text::Template (
221 SOURCE => $self->body,
223 my $body = $body_tmpl->fill_in( HASH => \%hash );
229 my @to = $cust_main->invoicing_list_emailonly;
230 #unless (@to) { #XXX do something }
232 my $conf = new FS::Conf;
235 'from' => $self->from ||
236 scalar( $conf->config('invoice_from', $cust_main->agentnum) ),
238 'subject' => $subject,
239 'html_body' => $body,
240 #XXX auto-make a text copy w/HTML::FormatText?
241 # alas, us luddite mutt/pine users just aren't that big a deal
246 =item send OPTION => VALUE
248 Fills in the template and sends it to the customer. Options are as for
255 send_email(generate_email($self->prepare(@_)));
258 # helper sub for package dates
259 my $ymd = sub { $_[0] ? time2str('%Y-%m-%d', $_[0]) : '' };
261 #return contexts and fill-in values
262 # If you add anything, be sure to add a description in
263 # httemplate/edit/msg_template.html.
265 { 'cust_main' => [qw(
266 display_custnum agentnum agent_name
269 name name_short contact contact_firstlast
270 address1 address2 city county state zip
275 ship_last ship_first ship_company
276 ship_name ship_name_short ship_contact ship_contact_firstlast
277 ship_address1 ship_address2 ship_city ship_county ship_state ship_zip
279 ship_daytime ship_night ship_fax
281 payby paymask payname paytype payip
282 num_cancelled_pkgs num_ncancelled_pkgs num_pkgs
283 classname categoryname
285 invoicing_list_emailonly
286 cust_status ucfirst_cust_status cust_statuscolor
290 [ signupdate_ymd => sub { time2str('%Y-%m-%d', shift->signupdate) } ],
291 [ dundate_ymd => sub { time2str('%Y-%m-%d', shift->dundate) } ],
292 [ paydate_my => sub { sprintf('%02d/%04d', shift->paydate_monthyear) } ],
293 [ otaker_first => sub { shift->access_user->first } ],
294 [ otaker_last => sub { shift->access_user->last } ],
298 pkgnum pkg_label pkg_label_long
302 start_date setup bill last_bill
306 [ cancel => sub { shift->getfield('cancel') } ], # grrr...
307 [ start_ymd => sub { $ymd->(shift->getfield('start_date')) } ],
308 [ setup_ymd => sub { $ymd->(shift->getfield('setup')) } ],
309 [ next_bill_ymd => sub { $ymd->(shift->getfield('bill')) } ],
310 [ last_bill_ymd => sub { $ymd->(shift->getfield('last_bill')) } ],
311 [ adjourn_ymd => sub { $ymd->(shift->getfield('adjourn')) } ],
312 [ susp_ymd => sub { $ymd->(shift->getfield('susp')) } ],
313 [ expire_ymd => sub { $ymd->(shift->getfield('expire')) } ],
314 [ cancel_ymd => sub { $ymd->(shift->getfield('cancel')) } ],
319 #XXX not really thinking about cust_bill substitutions quite yet
324 [ password => sub { shift->getfield('_password') } ],
325 ], # for welcome messages
330 my ($self, %opts) = @_;
333 [ 'alerter_msgnum', 'alerter_template', '', '' ],
334 [ 'cancel_msgnum', 'cancelmessage', 'cancelsubject', '' ],
335 [ 'decline_msgnum', 'declinetemplate', '', '' ],
336 [ 'impending_recur_msgnum', 'impending_recur_template', '', '' ],
337 [ 'welcome_msgnum', 'welcome_email', 'welcome_email-subject', 'welcome_email-from' ],
338 [ 'warning_msgnum', 'warning_email', 'warning_email-subject', 'warning_email-from' ],
341 my $conf = new FS::Conf;
342 my @agentnums = ('', map {$_->agentnum} qsearch('agent', {}));
343 foreach my $agentnum (@agentnums) {
345 my ($newname, $oldname, $subject, $from) = @$_;
346 if ($conf->exists($oldname, $agentnum)) {
347 my $new = new FS::msg_template({
348 'msgname' => $oldname,
349 'agentnum' => $agentnum,
350 'from_addr' => ($from && $conf->config($from, $agentnum)) ||
351 $conf->config('invoice_from', $agentnum),
352 'subject' => ($subject && $conf->config($subject, $agentnum)) || '',
353 'mime_type' => 'text/html',
354 'body' => join('<BR>',$conf->config($oldname, $agentnum)),
356 my $error = $new->insert;
357 die $error if $error;
358 $conf->set($newname, $new->msgnum, $agentnum);
359 $conf->delete($oldname, $agentnum);
360 $conf->delete($from, $agentnum) if $from;
361 $conf->delete($subject, $agentnum) if $subject;
373 L<FS::Record>, schema.html from the base documentation.