X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fmsg_template.pm;h=33e150ae338dc7743f20b04b9eabbb806a5d029a;hp=e38346a66d377386ceadfa39548e039fb685081a;hb=HEAD;hpb=963b977d201a71e85781bcbf6732795728e9fc0f diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index e38346a66..33e150ae3 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -1,25 +1,17 @@ package FS::msg_template; +use base qw( FS::Record ); use strict; -use base qw( FS::Record ); -use Text::Template; -use FS::Misc qw( generate_email send_email ); +use vars qw( $DEBUG $conf ); + use FS::Conf; -use FS::Record qw( qsearch qsearchs ); -use FS::UID qw( dbh ); +use FS::Record qw( qsearch qsearchs dbh ); -use FS::cust_main; use FS::cust_msg; use FS::template_content; -use Date::Format qw( time2str ); -use HTML::Entities qw( decode_entities encode_entities ) ; -use HTML::FormatText; -use HTML::TreeBuilder; - -use File::Temp; -use IPC::Run qw(run); -use vars qw( $DEBUG $conf ); +use Date::Format qw(time2str); +use PDF::WebKit; FS::UID->install_callback( sub { $conf = new FS::Conf; } ); @@ -44,6 +36,12 @@ FS::msg_template - Object methods for msg_template records $error = $record->check; +=head1 NOTE + +This uses a table-per-subclass ORM strategy, which is a somewhat cleaner +version of what we do elsewhere with _option tables. We could easily extract +that functionality into a base class, or even into FS::Record itself. + =head1 DESCRIPTION An FS::msg_template object represents a customer message template. @@ -57,6 +55,9 @@ supported: =item msgname - Name of the template. This will appear in the user interface; if it needs to be localized for some users, add it to the message catalog. +=item msgclass - The L subclass that this should belong to. +Defaults to 'email'. + =item agentnum - Agent associated with this template. Can be NULL for a global template. @@ -64,7 +65,11 @@ global template. =item from_addr - Source email address. -=item disabled - disabled ('Y' or NULL). +=item bcc_addr - Bcc all mail to this address. + +=item disabled - disabled (NULL for not-disabled and selectable, 'D' for a +draft of a one-time message, 'C' for a completed one-time message, 'Y' for a +normal template disabled by user action). =back @@ -85,40 +90,72 @@ points to. You can ask the object for a copy with the I method. sub table { 'msg_template'; } +sub extension_table { ''; } # subclasses don't HAVE to have extensions + +sub _rebless { + my $self = shift; + return '' unless $self->msgclass; + my $class = 'FS::msg_template::' . $self->msgclass; + eval "use $class;"; + bless($self, $class) unless $@; + warn "Error loading msg_template msgclass: " . $@ if $@; #or die? + + # merge in the extension fields (but let fields in $self override them) + # except don't ever override the extension's primary key, it's immutable + if ( $self->msgnum and $self->extension_table ) { + my $extension = $self->_extension; + if ( $extension ) { + my $ext_key = $extension->get($extension->primary_key); + $self->{Hash} = { $extension->hash, + $self->hash, + $extension->primary_key => $ext_key + }; + } + } + + $self; +} + +# Returns the subclass-specific extension record for this object. For internal +# use only; everyone else is supposed to think of this as a single record. + +sub _extension { + my $self = shift; + if ( $self->extension_table and $self->msgnum ) { + local $FS::Record::nowarn_classload = 1; + return qsearchs($self->extension_table, { msgnum => $self->msgnum }); + } + return; +} + =item insert [ CONTENT ] Adds this record to the database. If there is an error, returns the error, otherwise returns false. -A default (no locale) L object will be created. CONTENT -is an optional hash containing 'subject' and 'body' for this object. - =cut sub insert { my $self = shift; - my %content = @_; + $self->_rebless; my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; - my $dbh = dbh; my $error = $self->SUPER::insert; - if ( !$error ) { - $content{'msgnum'} = $self->msgnum; - $content{'subject'} ||= ''; - $content{'body'} ||= ''; - my $template_content = new FS::template_content (\%content); - $error = $template_content->insert; + # calling _extension at this point makes it copy the msgnum, so links work + if ( $self->extension_table ) { + local $FS::Record::nowarn_classload = 1; + my $extension = FS::Record->new($self->extension_table, { $self->hash }); + $error ||= $extension->insert; } if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; + dbh->rollback if $oldAutoCommit; + } else { + dbh->commit if $oldAutoCommit; } - - $dbh->commit if $oldAutoCommit; - return; + $error; } =item delete @@ -127,61 +164,73 @@ Delete this record from the database. =cut -# the delete method can be inherited from FS::Record +sub delete { + my $self = shift; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + + my $error; + my $extension = $self->_extension; + if ( $extension ) { + $error = $extension->delete; + } + + $error ||= $self->SUPER::delete; + + if ( $error ) { + dbh->rollback if $oldAutoCommit; + } else { + dbh->commit if $oldAutoCommit; + } + $error; +} -=item replace [ OLD_RECORD ] [ CONTENT ] +=item replace [ OLD_RECORD ] Replaces the OLD_RECORD with this one in the database. If there is an error, returns the error, otherwise returns false. -CONTENT is an optional hash containing 'subject', 'body', and 'locale'. If -supplied, an L object will be created (or modified, if -one already exists for this locale). - =cut sub replace { - my $self = shift; - my $old = ( ref($_[0]) and $_[0]->isa('FS::Record') ) - ? shift - : $self->replace_old; - my %content = @_; - + my $new = shift; + my $old = shift || $new->replace_old; + my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; - my $dbh = dbh; - - my $error = $self->SUPER::replace($old); - - if ( !$error and %content ) { - $content{'locale'} ||= ''; - my $new_content = qsearchs('template_content', { - 'msgnum' => $self->msgnum, - 'locale' => $content{'locale'}, - } ); - if ( $new_content ) { - $new_content->subject($content{'subject'}); - $new_content->body($content{'body'}); - $error = $new_content->replace; - } - else { - $content{'msgnum'} = $self->msgnum; - $new_content = new FS::template_content \%content; - $error = $new_content->insert; - } + + my $error = $new->SUPER::replace($old, @_); + + my $extension = $new->_extension; + if ( $extension ) { + # merge changes into the extension record and replace it + $extension->{Hash} = { $extension->hash, $new->hash }; + $error ||= $extension->replace; } if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; + dbh->rollback if $oldAutoCommit; + } else { + dbh->commit if $oldAutoCommit; } - - warn "committing FS::msg_template->replace\n" if $DEBUG and $oldAutoCommit; - $dbh->commit if $oldAutoCommit; - return; + $error; } - +sub replace_check { + my $self = shift; + my $old = $self->replace_old; + # don't allow changing msgclass, except null to not-null (for upgrade) + if ( $old->msgclass ) { + if ( !$self->msgclass ) { + $self->set('msgclass', $old->msgclass); + } elsif ( $old->msgclass ne $self->msgclass ) { + return "Can't change message template class from ".$old->msgclass. + " to ".$self->msgclass."."; + } + } + ''; +} =item check @@ -202,8 +251,12 @@ sub check { || $self->ut_text('msgname') || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum') || $self->ut_textn('mime_type') - || $self->ut_enum('disabled', [ '', 'Y' ] ) + || $self->ut_enum('disabled', [ '', 'Y', 'D', 'S' ] ) || $self->ut_textn('from_addr') + || $self->ut_textn('bcc_addr') + # fine for now, but change this to some kind of dynamic check if we + # ever have more than two msgclasses + || $self->ut_enum('msgclass', [ qw(email http) ]), ; return $error if $error; @@ -212,25 +265,10 @@ sub check { $self->SUPER::check; } -=item content_locales - -Returns a hashref of the L objects attached to -this template, with the locale as key. - -=cut - -sub content_locales { - my $self = shift; - return $self->{'_content_locales'} ||= +{ - map { $_->locale , $_ } - qsearch('template_content', { 'msgnum' => $self->msgnum }) - }; -} - =item prepare OPTION => VALUE -Fills in the template and returns a hash of the 'from' address, 'to' -addresses, subject line, and body. +Fills in the template and returns an L object, containing the +message to be sent. This method must be provided by the subclass. Options are passed as a list of name/value pairs: @@ -238,7 +276,7 @@ Options are passed as a list of name/value pairs: =item cust_main -Customer object (required). +Customer object =item object @@ -274,18 +312,24 @@ A hash reference of additional substitutions =cut sub prepare { + die "unimplemented"; +} + +=item prepare_substitutions OPTION => VALUE ... + +Takes the same arguments as L, and returns a hashref of the +substitution variables. + +=cut + +sub prepare_substitutions { my( $self, %opt ) = @_; - my $cust_main = $opt{'cust_main'} or die 'cust_main required'; - my $object = $opt{'object'} or die 'object required'; + my $cust_main = $opt{'cust_main'}; # or die 'cust_main required'; + my $object = $opt{'object'}; # or die 'object required'; - # localization - my $locale = $cust_main->locale || ''; - warn "no locale for cust#".$cust_main->custnum."; using default content\n" - if $DEBUG and !$locale; - my $content = $self->content($cust_main->locale); - warn "preparing template '".$self->msgname."' to cust#".$cust_main->custnum."\n" - if($DEBUG); + warn "preparing substitutions for '".$self->msgname."'\n" + if $DEBUG; my $subs = $self->substitutions; @@ -293,7 +337,8 @@ sub prepare { # create substitution table ### my %hash; - my @objects = ($cust_main); + my @objects = (); + push @objects, $cust_main if $cust_main; my @prefixes = (''); my $svc; if( ref $object ) { @@ -336,106 +381,19 @@ sub prepare { $hash{$_} = $opt{substitutions}->{$_} foreach keys %{$opt{substitutions}}; } - $_ = encode_entities($_ || '') foreach values(%hash); - - ### - # clean up template - ### - my $subject_tmpl = new Text::Template ( - TYPE => 'STRING', - SOURCE => $content->subject, - ); - my $subject = $subject_tmpl->fill_in( HASH => \%hash ); - - my $body = $content->body; - my ($skin, $guts) = eviscerate($body); - @$guts = map { - $_ = decode_entities($_); # turn all punctuation back into itself - s/\r//gs; # remove \r's - s/]*>/\n/gsi; # and
tags - s/

/\n/gsi; # and

- s/<\/p>//gsi; # and

- s/\240/ /gs; # and   - $_ - } @$guts; - - $body = '{ use Date::Format qw(time2str); "" }'; - while(@$skin || @$guts) { - $body .= shift(@$skin) || ''; - $body .= shift(@$guts) || ''; - } - - ### - # fill-in - ### - - my $body_tmpl = new Text::Template ( - TYPE => 'STRING', - SOURCE => $body, - ); - - $body = $body_tmpl->fill_in( HASH => \%hash ); - - ### - # and email - ### - - my @to; - if ( exists($opt{'to'}) ) { - @to = split(/\s*,\s*/, $opt{'to'}); - } - else { - @to = $cust_main->invoicing_list_emailonly; - } - # no warning when preparing with no destination - - my $from_addr = $self->from_addr; - - if ( !$from_addr ) { - if ( $opt{'from_config'} ) { - $from_addr = scalar( $conf->config($opt{'from_config'}, - $cust_main->agentnum) ); - } - $from_addr ||= scalar( $conf->config('invoice_from', - $cust_main->agentnum) ); - } -# my @cust_msg = (); -# if ( $conf->exists('log_sent_mail') and !$opt{'preview'} ) { -# my $cust_msg = FS::cust_msg->new({ -# 'custnum' => $cust_main->custnum, -# 'msgnum' => $self->msgnum, -# 'status' => 'prepared', -# }); -# $cust_msg->insert; -# @cust_msg = ('cust_msg' => $cust_msg); -# } - - ( - 'custnum' => $cust_main->custnum, - 'msgnum' => $self->msgnum, - 'from' => $from_addr, - 'to' => \@to, - 'bcc' => $self->bcc_addr || undef, - 'subject' => $subject, - 'html_body' => $body, - 'text_body' => HTML::FormatText->new(leftmargin => 0, rightmargin => 70 - )->format( HTML::TreeBuilder->new_from_content($body) ), - ); - + return \%hash; } -=item send OPTION => VALUE +=item send OPTION => VALUE ... -Fills in the template and sends it to the customer. Options are as for -'prepare'. +Creates a message with L (taking all the same options) and sends it. =cut -# broken out from prepare() in case we want to queue the sending, -# preview it, etc. sub send { my $self = shift; - send_email(generate_email($self->prepare(@_))); + my $cust_msg = $self->prepare(@_); + $self->send_prepared($cust_msg); } =item render OPTION => VALUE ... @@ -447,34 +405,26 @@ Options are as for 'prepare', but 'from' and 'to' are meaningless. =cut +# XXX not sure where this ends up post-refactoring--a separate template +# class? it doesn't use the same rendering OR output machinery as ::email + # will also have options to set paper size, margins, etc. sub render { my $self = shift; - eval "use PDF::WebKit"; - die $@ if $@; my %opt = @_; my %hash = $self->prepare(%opt); my $html = $hash{'html_body'}; - my $tmp = 'msg'.$self->msgnum.'-'.time2str('%Y%m%d', time).'-XXXXXXXX'; - my $dir = "$FS::UID::cache_dir/cache.$FS::UID::datasrc"; - # Graphics/stylesheets should probably go in /var/www on the Freeside # machine. + my $script_path = `/usr/bin/which freeside-wkhtmltopdf`; + chomp $script_path; my $kit = PDF::WebKit->new(\$html); #%options # hack to use our wrapper script - $kit->configure(sub { shift->wkhtmltopdf('freeside-wkhtmltopdf') }); - my $fh = File::Temp->new( - TEMPLATE => $tmp, - DIR => $dir, - UNLINK => 0, - SUFFIX => '.pdf' - ); + $kit->configure(sub { shift->wkhtmltopdf($script_path) }); - print $fh $kit->to_pdf; - close $fh; - return $fh->filename; + $kit->to_pdf; } =item print OPTIONS @@ -484,13 +434,10 @@ Render a PDF and send it to the printer. OPTIONS are as for 'render'. =cut sub print { - my $file = render(@_); - my @lpr = $conf->config('lpr'); - run ([@lpr, '-r'], '<', $file) - or die "lpr error:\n$?\n"; + my( $self, %opt ) = @_; + do_print( [ $self->render(%opt) ], agentnum=>$opt{cust_main}->agentnum ); } - # helper sub for package dates my $ymd = sub { $_[0] ? time2str('%Y-%m-%d', $_[0]) : '' }; @@ -511,12 +458,21 @@ my $usage_warning = sub { return ['', '', '']; }; -#my $conf = new FS::Conf; - #return contexts and fill-in values # If you add anything, be sure to add a description in # httemplate/edit/msg_template.html. sub substitutions { + my $payinfo_sub = sub { + my $obj = shift; + ($obj->payby eq 'CARD' || $obj->payby eq 'CHEK') + ? $obj->paymask + : $obj->decrypt($obj->payinfo) + }; + my $payinfo_end = sub { + my $obj = shift; + my $payinfo = &$payinfo_sub($obj); + substr($payinfo, -4); + }; { 'cust_main' => [qw( display_custnum agentnum agent_name @@ -537,7 +493,7 @@ sub substitutions { balance credit_limit invoicing_list_emailonly - cust_status ucfirst_cust_status cust_statuscolor + cust_status ucfirst_cust_status cust_statuscolor cust_status_label signupdate dundate packages recurdates @@ -568,6 +524,9 @@ sub substitutions { [ company_phonenum => sub { $conf->config('company_phonenum', shift->agentnum) } ], + [ selfservice_server_base_url => sub { + $conf->config('selfservice_server-base_url') #, shift->agentnum) + } ], ], # next_bill_date 'cust_pkg' => [qw( @@ -602,7 +561,11 @@ sub substitutions { 'cust_bill' => [qw( invnum _date - )], + _date_pretty + due_date + ), + [ due_date2str => sub { shift->due_date2str('short') } ], + ], #XXX not really thinking about cust_bill substitutions quite yet # for welcome and limit warning messages @@ -656,11 +619,17 @@ sub substitutions { # overrides the one in cust_main in cases where a cust_pay is passed [ payby => sub { FS::payby->shortname(shift->payby) } ], [ date => sub { time2str("%a %B %o, %Y", shift->_date) } ], - [ payinfo => sub { - my $cust_pay = shift; - ($cust_pay->payby eq 'CARD' || $cust_pay->payby eq 'CHEK') ? - $cust_pay->paymask : $cust_pay->decrypt($cust_pay->payinfo) - } ], + [ 'payinfo' => $payinfo_sub ], + [ 'payinfo_end' => $payinfo_end ], + ], + # for refund receipts + 'cust_refund' => [ + 'refundnum', + [ refund => sub { sprintf("%.2f", shift->refund) } ], + [ payby => sub { FS::payby->shortname(shift->payby) } ], + [ date => sub { time2str("%a %B %o, %Y", shift->_date) } ], + [ 'payinfo' => $payinfo_sub ], + [ 'payinfo_end' => $payinfo_end ], ], # for payment decline messages # try to support all cust_pay fields @@ -672,30 +641,19 @@ sub substitutions { [ paid => sub { sprintf("%.2f", shift->paid) } ], [ payby => sub { FS::payby->shortname(shift->payby) } ], [ date => sub { time2str("%a %B %o, %Y", shift->_date) } ], - [ payinfo => sub { - my $pending = shift; - ($pending->payby eq 'CARD' || $pending->payby eq 'CHEK') ? - $pending->paymask : $pending->decrypt($pending->payinfo) - } ], + [ 'payinfo' => $payinfo_sub ], + [ 'payinfo_end' => $payinfo_end ], ], }; } =item content LOCALE -Returns the L object appropriate to LOCALE, if there -is one. If not, returns the one with a NULL locale. +Stub, returns nothing. =cut -sub content { - my $self = shift; - my $locale = shift; - qsearchs('template_content', - { 'msgnum' => $self->msgnum, 'locale' => $locale }) || - qsearchs('template_content', - { 'msgnum' => $self->msgnum, 'locale' => '' }); -} +sub content {} =item agent @@ -703,33 +661,35 @@ Returns the L object for this template. =cut -sub agent { - qsearchs('agent', { 'agentnum' => $_[0]->agentnum }); -} - sub _upgrade_data { my ($self, %opts) = @_; + ### + # First move any historical templates in config to real message templates + ### + my @fixes = ( [ 'alerter_msgnum', 'alerter_template', '', '', '' ], [ 'cancel_msgnum', 'cancelmessage', 'cancelsubject', '', '' ], [ 'decline_msgnum', 'declinetemplate', '', '', '' ], [ 'impending_recur_msgnum', 'impending_recur_template', '', '', 'impending_recur_bcc' ], [ 'payment_receipt_msgnum', 'payment_receipt_email', '', '', '' ], - [ 'welcome_msgnum', 'welcome_email', 'welcome_email-subject', 'welcome_email-from', '' ], - [ 'warning_msgnum', 'warning_email', 'warning_email-subject', 'warning_email-from', '' ], + [ 'welcome_msgnum', 'welcome_email', 'welcome_email-subject', 'welcome_email-from', '', 'welcome_email-mimetype' ], + [ 'threshold_warning_msgnum', 'warning_email', 'warning_email-subject', 'warning_email-from', 'warning_email-cc', 'warning_email-mimetype' ], ); my @agentnums = ('', map {$_->agentnum} qsearch('agent', {})); foreach my $agentnum (@agentnums) { foreach (@fixes) { - my ($newname, $oldname, $subject, $from, $bcc) = @$_; + my ($newname, $oldname, $subject, $from, $bcc, $mimetype) = @$_; + if ($conf->exists($oldname, $agentnum)) { my $new = new FS::msg_template({ + 'msgclass' => 'email', 'msgname' => $oldname, 'agentnum' => $agentnum, 'from_addr' => ($from && $conf->config($from, $agentnum)) || '', - 'bcc_addr' => ($bcc && $conf->config($from, $agentnum)) || '', + 'bcc_addr' => ($bcc && $conf->config($bcc, $agentnum)) || '', 'subject' => ($subject && $conf->config($subject, $agentnum)) || '', 'mime_type' => 'text/html', 'body' => join('
',$conf->config($oldname, $agentnum)), @@ -740,9 +700,74 @@ sub _upgrade_data { $conf->delete($oldname, $agentnum); $conf->delete($from, $agentnum) if $from; $conf->delete($subject, $agentnum) if $subject; + $conf->delete($bcc, $agentnum) if $bcc; + $conf->delete($mimetype, $agentnum) if $mimetype; } } + + if ( $conf->exists('alert_expiration', $agentnum) ) { + my $msgnum = $conf->exists('alerter_msgnum', $agentnum); + my $template = FS::msg_template->by_key($msgnum) if $msgnum; + if (!$template) { + warn "template for alerter_msgnum $msgnum not found\n"; + next; + } + # this is now a set of billing events + foreach my $days (30, 15, 5) { + my $event = FS::part_event->new({ + 'agentnum' => $agentnum, + 'event' => "Card expiration warning - $days days", + 'eventtable' => 'cust_main', + 'check_freq' => '1d', + 'action' => 'notice', + 'disabled' => 'Y', #initialize first + }); + my $error = $event->insert( 'msgnum' => $msgnum ); + if ($error) { + warn "error creating expiration alert event:\n$error\n\n"; + next; + } + # make it work like before: + # only send each warning once before the card expires, + # only warn active customers, + # only warn customers with CARD/DCRD, + # only warn customers who get email invoices + my %conds = ( + 'once_every' => { 'run_delay' => '30d' }, + 'cust_paydate_within' => { 'within' => $days.'d' }, + 'cust_status' => { 'status' => { 'active' => 1 } }, + 'payby' => { 'payby' => { 'CARD' => 1, + 'DCRD' => 1, } + }, + 'message_email' => {}, + ); + foreach (keys %conds) { + my $condition = FS::part_event_condition->new({ + 'conditionname' => $_, + 'eventpart' => $event->eventpart, + }); + $error = $condition->insert( %{ $conds{$_} }); + if ( $error ) { + warn "error creating expiration alert event:\n$error\n\n"; + next; + } + } + $error = $event->initialize; + if ( $error ) { + warn "expiration alert event was created, but not initialized:\n$error\n\n"; + } + } # foreach $days + $conf->delete('alerter_msgnum', $agentnum); + $conf->delete('alert_expiration', $agentnum); + + } # if alerter_msgnum + } + + ### + # Move subject and body from msg_template to template_content + ### + foreach my $msg_template ( qsearch('msg_template', {}) ) { if ( $msg_template->subject || $msg_template->body ) { # create new default content @@ -761,61 +786,122 @@ sub _upgrade_data { } $content{body} = $body; $msg_template->set('body', ''); - my $error = $msg_template->replace(%content); die $error if $error; } - } -} -sub eviscerate { - # Every bit as pleasant as it sounds. - # - # We do this because Text::Template::Preprocess doesn't - # actually work. It runs the entire template through - # the preprocessor, instead of the code segments. Which - # is a shame, because Text::Template already contains - # the code to do this operation. - my $body = shift; - my (@outside, @inside); - my $depth = 0; - my $chunk = ''; - while($body || $chunk) { - my ($first, $delim, $rest); - # put all leading non-delimiters into $first - ($first, $rest) = - ($body =~ /^((?:\\[{}]|[^{}])*)(.*)$/s); - $chunk .= $first; - # put a leading delimiter into $delim if there is one - ($delim, $rest) = - ($rest =~ /^([{}]?)(.*)$/s); - - if( $delim eq '{' ) { - $chunk .= '{'; - if( $depth == 0 ) { - push @outside, $chunk; - $chunk = ''; - } - $depth++; + if ( !$msg_template->msgclass ) { + # set default message class + $msg_template->set('msgclass', 'email'); + my $error = $msg_template->replace; + die $error if $error; } - elsif( $delim eq '}' ) { - $depth--; - if( $depth == 0 ) { - push @inside, $chunk; - $chunk = ''; + } + + ### + # Add new-style default templates if missing + ### + $self->_populate_initial_data; + + ### + # Move welcome_msgnum to an export + ### + + #upgrade_journal loaded by _populate_initial_data + unless (FS::upgrade_journal->is_done('msg_template__welcome_export')) { + if (my $msgnum = $conf->config('welcome_msgnum')) { + eval "use FS::part_export;"; + die $@ if $@; + eval "use FS::part_svc;"; + die $@ if $@; + eval "use FS::export_svc;"; + die $@ if $@; + #create the export + my $part_export = new FS::part_export { + 'exportname' => 'Welcome Email', + 'exporttype' => 'send_email' + }; + my $error = $part_export->insert({ + 'to_customer' => 1, + 'insert_template' => $msgnum, + # replicate blank options that would be generated by UI, + # to avoid unexpected results from not having them exist + 'to_address' => '', + 'replace_template' => 0, + 'suspend_template' => 0, + 'unsuspend_template' => 0, + 'delete_template' => 0, + }); + die $error if $error; + #attach it to part_svcs + my @welcome_exclude_svcparts = $conf->config('svc_acct_welcome_exclude'); + foreach my $part_svc ( + qsearch('part_svc',{ 'svcdb' => 'svc_acct', 'disabled' => '' }) + ) { + next if grep { $_ eq $part_svc->svcpart } @welcome_exclude_svcparts; + my $export_svc = new FS::export_svc { + 'exportnum' => $part_export->exportnum, + 'svcpart' => $part_svc->svcpart, + }; + $error = $export_svc->insert; + die $error if $error; } - $chunk .= '}'; + #remove the old confs + $error = $conf->delete('welcome_msgnum'); + die $error if $error; + $error = $conf->delete('svc_acct_welcome_exclude'); + die $error if $error; } - else { - # no more delimiters - if( $depth == 0 ) { - push @outside, $chunk . $rest; - } # else ? something wrong - last; + FS::upgrade_journal->set_done('msg_template__welcome_export'); + } + + + ### Fix dump-email_to (needs to happen after _populate_initial_data) + if ($conf->config('dump-email_to')) { + # anyone who still uses dump-email_to should have just had this created + my ($msg_template) = qsearch('msg_template',{ msgname => 'System log' }); + if ($msg_template) { + eval "use FS::log_email;"; + die $@ if $@; + my $log_email = new FS::log_email { + 'context' => 'Cron::backup', + 'min_level' => 1, + 'msgnum' => $msg_template->msgnum, + 'to_addr' => $conf->config('dump-email_to'), + }; + my $error = $log_email->insert; + die $error if $error; + $conf->delete('dump-email_to'); } - $body = $rest; } - (\@outside, \@inside); + +} + +sub _populate_initial_data { #class method + #my($class, %opts) = @_; + #my $class = shift; + + eval "use FS::msg_template::InitialData;"; + die $@ if $@; + eval "use FS::upgrade_journal;"; + die $@ if $@; + + my $initial_data = FS::msg_template::InitialData->_initial_data; + + foreach my $hash ( @$initial_data ) { + + next if $hash->{_conf} && $conf->config( $hash->{_conf} ); + next if $hash->{_upgrade_journal} && FS::upgrade_journal->is_done( $hash->{_upgrade_journal} ); + + my $msg_template = new FS::msg_template($hash); + my $error = $msg_template->insert( @{ $hash->{_insert_args} || [] } ); + die $error if $error; + + $conf->set( $hash->{_conf}, $msg_template->msgnum ) if $hash->{_conf}; + FS::upgrade_journal->set_done( $hash->{_upgrade_journal} ) if $hash->{_upgrade_journal}; + + } + } =back