X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fmsg_template.pm;h=180e9de4db8c4aec36701c086cc080a771d9a622;hb=33f1c704766af0621159d5a8453379b6706d8c8a;hp=94d478f6f430ffc3f2305b69e8ddf2415b7c3029;hpb=167dbdad01e2c1b62fd9be43cc05212e8c874a02;p=freeside.git diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index 94d478f6f..180e9de4d 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -4,22 +4,9 @@ use base qw( FS::Record ); use strict; use vars qw( $DEBUG $conf ); -use Date::Format qw( time2str ); -use File::Temp; -use IPC::Run qw(run); -use Text::Template; - -use HTML::Entities qw( decode_entities encode_entities ) ; -use HTML::FormatText; -use HTML::TreeBuilder; -use Encode; - -use FS::Misc qw( generate_email send_email do_print ); use FS::Conf; use FS::Record qw( qsearch qsearchs ); -use FS::UID qw( dbh ); -use FS::cust_main; use FS::cust_msg; use FS::template_content; @@ -59,6 +46,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. @@ -66,6 +56,8 @@ global template. =item from_addr - Source email address. +=item bcc_addr - Bcc all mail to this address. + =item disabled - disabled ('Y' or NULL). =back @@ -87,41 +79,20 @@ points to. You can ask the object for a copy with the I method. sub table { 'msg_template'; } +sub _rebless { + my $self = shift; + my $class = 'FS::msg_template::' . $self->msgclass; + eval "use $class;"; + bless($self, $class) unless $@; + $self; +} + =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 = @_; - - 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; - } - - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - - $dbh->commit if $oldAutoCommit; - return; -} +# inherited =item delete @@ -129,61 +100,31 @@ Delete this record from the database. =cut -# the delete method can be inherited from FS::Record +# inherited =item replace [ OLD_RECORD ] [ CONTENT ] 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 { +# inherited + +sub replace_check { my $self = shift; - my $old = ( ref($_[0]) and $_[0]->isa('FS::Record') ) - ? shift - : $self->replace_old; - my %content = @_; - - 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 $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); + } else { + return "Can't change message template class from ".$old->msgclass. + " to ".$self->msgclass."."; } } - - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - - warn "committing FS::msg_template->replace\n" if $DEBUG and $oldAutoCommit; - $dbh->commit if $oldAutoCommit; - return; + ''; } - - =item check @@ -206,6 +147,10 @@ sub check { || $self->ut_textn('mime_type') || $self->ut_enum('disabled', [ '', 'Y' ] ) || $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; @@ -214,25 +159,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: @@ -276,18 +206,23 @@ 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'; - # localization - my $locale = $cust_main && $cust_main->locale || ''; - warn "no locale for cust#".$cust_main->custnum."; using default content\n" - if $DEBUG and $cust_main && !$locale; - my $content = $self->content($locale); - - warn "preparing template '".$self->msgname."\n" + warn "preparing substitutions for '".$self->msgname."'\n" if $DEBUG; my $subs = $self->substitutions; @@ -340,110 +275,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'}); - } elsif ( $cust_main ) { - @to = $cust_main->invoicing_list_emailonly; - } else { - die 'no To: address or cust_main object specified'; - } - - my $from_addr = $self->from_addr; - - if ( !$from_addr ) { - - my $agentnum = $cust_main ? $cust_main->agentnum : ''; - - if ( $opt{'from_config'} ) { - $from_addr = $conf->config($opt{'from_config'}, $agentnum); - } - $from_addr ||= $conf->invoice_from_full($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); -# } - - my $text_body = encode('UTF-8', - HTML::FormatText->new(leftmargin => 0, rightmargin => 70) - ->format( HTML::TreeBuilder->new_from_content($body) ) - ); - ( - 'custnum' => ( $cust_main ? $cust_main->custnum : ''), - 'msgnum' => $self->msgnum, - 'from' => $from_addr, - 'to' => \@to, - 'bcc' => $self->bcc_addr || undef, - 'subject' => $subject, - 'html_body' => $body, - 'text_body' => $text_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 ... @@ -455,6 +299,9 @@ 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 { @@ -467,9 +314,11 @@ sub render { # 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') }); + $kit->configure(sub { shift->wkhtmltopdf($script_path) }); $kit->to_pdf; } @@ -505,8 +354,6 @@ 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. @@ -601,8 +448,9 @@ sub substitutions { _date _date_pretty due_date - due_date2str - )], + ), + [ due_date2str => sub { shift->due_date2str('short') } ], + ], #XXX not really thinking about cust_bill substitutions quite yet # for welcome and limit warning messages @@ -683,19 +531,11 @@ sub substitutions { =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 @@ -824,10 +664,16 @@ sub _upgrade_data { } $content{body} = $body; $msg_template->set('body', ''); - my $error = $msg_template->replace(%content); die $error if $error; } + + if ( !$msg_template->msgclass ) { + # set default message class + $msg_template->set('msgclass', 'email'); + my $error = $msg_template->replace; + die $error if $error; + } } ### @@ -860,56 +706,6 @@ sub _populate_initial_data { #class method } -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++; - } - elsif( $delim eq '}' ) { - $depth--; - if( $depth == 0 ) { - push @inside, $chunk; - $chunk = ''; - } - $chunk .= '}'; - } - else { - # no more delimiters - if( $depth == 0 ) { - push @outside, $chunk . $rest; - } # else ? something wrong - last; - } - $body = $rest; - } - (\@outside, \@inside); -} - =back =head1 BUGS