[freeside-commits] branch master updated. 2631ae913a1546b2f54f1355017e34b8b4a088bd

Mark Wells mark at 420.am
Sat Aug 29 13:59:14 PDT 2015


The branch, master has been updated
       via  2631ae913a1546b2f54f1355017e34b8b4a088bd (commit)
       via  a008b601383d5693a197f4bf57ed5ba7887f3065 (commit)
       via  81e562e6067ccf33c24ab3713163a0eefb1438bd (commit)
       via  46b9a9665971f30562b0a6a6231561116399d3a0 (commit)
      from  42837bd9ef4c47b1885564c2e56c4ca0f1e36e77 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 2631ae913a1546b2f54f1355017e34b8b4a088bd
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Aug 29 13:58:58 2015 -0700

    #21564: queueable sending

diff --git a/FS/FS/cust_msg.pm b/FS/FS/cust_msg.pm
index ec2c961..db02680 100644
--- a/FS/FS/cust_msg.pm
+++ b/FS/FS/cust_msg.pm
@@ -205,6 +205,25 @@ sub parts {
 
 =back
 
+=head1 SUBROUTINES
+
+=over 4
+
+=item process_send CUSTMSGNUM
+
+Given a C<cust_msg.custmsgnum> value, sends the message. It must already
+have been prepared (via L<FS::msg_template/prepare>).
+
+=cut
+
+sub process_send {
+  my $custmsgnum = shift;
+  my $cust_msg = FS::cust_msg->by_key($custmsgnum)
+    or die "cust_msg #$custmsgnum not found";
+  my $error = $cust_msg->send;
+  die $error if $error;
+}
+
 =head1 SEE ALSO
 
 L<FS::msg_template>, L<FS::cust_main>, L<FS::Record>.

commit a008b601383d5693a197f4bf57ed5ba7887f3065
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Aug 29 13:37:23 2015 -0700

    #21564, external message services: REST client

diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm
index d7d9f50..827bb98 100644
--- a/FS/FS/msg_template.pm
+++ b/FS/FS/msg_template.pm
@@ -35,6 +35,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.
@@ -81,20 +87,66 @@ points to.  You can ask the object for a copy with the I<hash> method.
 
 sub table { 'msg_template'; }
 
+sub extension_table { ''; } # subclasses don't HAVE to have extensions
+
 sub _rebless {
   my $self = shift;
   my $class = 'FS::msg_template::' . $self->msgclass;
   eval "use $class;";
   bless($self, $class) unless $@;
+
+  # merge in the extension fields
+  if ( $self->msgnum and $self->extension_table ) {
+    my $extension = $self->_extension;
+    if ( $extension ) {
+      $self->{Hash} = { $self->hash, $extension->hash };
+    }
+  }
+
   $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.
 
-# inherited
+=cut
+
+sub insert {
+  my $self = shift;
+  $self->_rebless;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  my $error = $self->SUPER::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;
+  } else {
+    dbh->commit if $oldAutoCommit;
+  }
+  $error;
+}
 
 =item delete
 
@@ -102,16 +154,56 @@ Delete this record from the database.
 
 =cut
 
-# inherited
+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.
 
 =cut
 
-# inherited
+sub replace {
+  my $new = shift;
+  my $old = shift || $new->replace_old;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  my $error = $new->SUPER::replace($old, @_);
+
+  my $extension = $new->_extension;
+  if ( $extension ) {
+    $error ||= $extension->replace;
+  }
+
+  if ( $error ) {
+    dbh->rollback if $oldAutoCommit;
+  } else {
+    dbh->commit if $oldAutoCommit;
+  }
+  $error;
+}
 
 sub replace_check {
   my $self = shift;
diff --git a/FS/FS/msg_template/email.pm b/FS/FS/msg_template/email.pm
index f8ebfa0..e6d5a5a 100644
--- a/FS/FS/msg_template/email.pm
+++ b/FS/FS/msg_template/email.pm
@@ -448,17 +448,10 @@ sub content {
 
 =cut
 
-=back
-
-=head2 CLASS METHODS
-
-=over 4
-
 =item send_prepared CUST_MSG
 
-Takes the CUST_MSG object and sends it to its recipient. This is a class 
-method because everything needed to send the message is stored in the 
-CUST_MSG already.
+Takes the CUST_MSG object and sends it to its recipient. The "smtpmachine"
+configuration option will be used to find the outgoing mail server.
 
 =cut
 
diff --git a/FS/FS/msg_template/http.pm b/FS/FS/msg_template/http.pm
new file mode 100644
index 0000000..51dfcff
--- /dev/null
+++ b/FS/FS/msg_template/http.pm
@@ -0,0 +1,155 @@
+package FS::msg_template::http;
+use base qw( FS::msg_template );
+
+use strict;
+use vars qw( $DEBUG $conf );
+
+# needed to talk to the external service
+use LWP::UserAgent;
+use HTTP::Request::Common;
+use JSON;
+
+# needed to manage prepared messages
+use FS::cust_msg;
+
+our $DEBUG = 1;
+our $me = '[FS::msg_template::http]';
+
+sub extension_table { 'msg_template_http' }
+
+=head1 NAME
+
+FS::msg_template::http - Send messages via a web service.
+
+=head1 DESCRIPTION
+
+FS::msg_template::http is a message processor in which the message is exported
+to a web service, at both the prepare and send stages.
+
+=head1 METHODS
+
+=cut
+
+sub check {
+  my $self = shift;
+  return 
+       $self->ut_textn('prepare_url')
+    || $self->ut_textn('send_url')
+    || $self->ut_textn('username')
+    || $self->ut_textn('password')
+    || $self->ut_anything('content')
+    || $self->SUPER::check;
+}
+
+sub prepare {
+
+  my( $self, %opt ) = @_;
+
+  my $json = JSON->new->canonical(1);
+
+  my $cust_main = $opt{'cust_main'}; # or die 'cust_main required';
+  my $object = $opt{'object'} or die 'object required';
+
+  my $hashref = $self->prepare_substitutions(%opt);
+
+  my $document = $json->decode( $self->content || '{}' );
+  $document = {
+    'msgname' => $self->msgname,
+    'msgtype' => $opt{'msgtype'},
+    %$document,
+    %$hashref
+  };
+
+  my $request_content = $json->encode($document);
+  warn "$me ".$self->prepare_url."\n" if $DEBUG;
+  warn "$request_content\n\n" if $DEBUG > 1;
+  my $ua = LWP::UserAgent->new;
+  my $request = POST(
+    $self->prepare_url,
+    'Content-Type' => 'application/json',
+    'Content' => $request_content,
+  );
+  if ( $self->username ) {
+    $request->authorization_basic( $self->username, $self->password );
+  }
+  my $response = $ua->request($request);
+  warn "$me received:\n" . $response->as_string . "\n\n" if $DEBUG;
+
+  my $cust_msg = FS::cust_msg->new({
+      'custnum'   => $cust_main->custnum,
+      'msgnum'    => $self->msgnum,
+      '_date'     => time,
+      'msgtype'   => ($opt{'msgtype'} || ''),
+  });
+
+  if ( $response->is_success ) {
+    $cust_msg->set(body => $response->decoded_content);
+    $cust_msg->set(status => 'prepared');
+  } else {
+    $cust_msg->set(status => 'failed');
+    $cust_msg->set(error => $response->decoded_content);
+  }
+
+  $cust_msg;
+}
+
+=item send_prepared CUST_MSG
+
+Takes the CUST_MSG object and sends it to its recipient.
+
+=cut
+
+sub send_prepared {
+  my $self = shift;
+  my $cust_msg = shift or die "cust_msg required";
+  # don't just fail if called as a class method
+  if (!ref $self) {
+    $self = $cust_msg->msg_template;
+  }
+
+  # use cust_msg->header for anything? we _could_...
+  my $request_content = $cust_msg->body;
+
+  warn "$me ".$self->send_url."\n" if $DEBUG;
+  warn "$request_content\n\n" if $DEBUG > 1;
+  my $ua = LWP::UserAgent->new;
+  my $request = POST(
+    $self->send_url,
+    'Content-Type' => 'application/json',
+    'Content' => $request_content,
+  );
+  if ( $self->username ) {
+    $request->authorization_basic( $self->username, $self->password );
+  }
+  my $response = $ua->request($request);
+  warn "$me received:\n" . $response->as_string . "\n\n" if $DEBUG;
+
+  my $error;
+  if ( $response->is_success ) {
+    $cust_msg->set(status => 'sent');
+  } else {
+    $error = $response->decoded_content;
+    $cust_msg->set(error => $error);
+    $cust_msg->set(status => 'failed');
+  }
+
+  if ( $cust_msg->custmsgnum ) {
+    $cust_msg->replace;
+  } else {
+    $cust_msg->insert;
+  }
+
+  $error;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
diff --git a/bin/msg_template_http-demo.pl b/bin/msg_template_http-demo.pl
new file mode 100755
index 0000000..8d184fc
--- /dev/null
+++ b/bin/msg_template_http-demo.pl
@@ -0,0 +1,76 @@
+=head1 NAME
+
+FS::msg_template::http example server.
+
+=head1 DESCRIPTION
+
+This is an incredibly crude Mojo web service for demonstrating how to talk 
+to the HTTP customer messaging interface in Freeside.
+
+It implements an endpoint for the "password reset" messaging case which 
+creates a simple password reset message using some template variables,
+and a "send" endpoint that just delivers the message by sendmail. The 
+configuration to use this as your password reset handler would be:
+
+prepare_url = 'http://localhost:3000/prepare/password_reset'
+send_url =    'http://localhost:3000/send'
+No username, no password, no additional content.
+
+=cut
+
+use Mojolicious::Lite;
+use Mojo::JSON qw(decode_json encode_json);
+use Email::Simple;
+use Email::Simple::Creator;
+use Email::Sender::Simple qw(sendmail);
+
+post '/prepare/password_reset' => sub {
+  my $self = shift;
+
+  my $json_data = $self->req->body;
+  #print STDERR $json_data;
+  my $input = decode_json($json_data);
+  if ( $input->{username} ) {
+    my $output = {
+      'to'      => $input->{invoicing_email},
+      'subject' => "Password reset for $input->{username}",
+      'body'    => "
+To complete your $input->{company_name} password reset, please go to 
+$input->{selfservice_server_base_url}/selfservice.cgi?action=process_forgot_password;session_id=$input->{session_id}
+
+This link will expire in 24 hours.",
+    };
+
+    return $self->render( json => $output );
+
+  } else {
+
+    return $self->render( text => 'Username required', status => 500 );
+
+  }
+};
+
+post '/send' => sub {
+  my $self = shift;
+
+  my $json_data = $self->req->body;
+  my $input = decode_json($json_data);
+  my $email = Email::Simple->create(
+    header => [
+      From    => $ENV{USER}.'@localhost',
+      To      => $input->{to},
+      Subject => $input->{subject},
+    ],
+    body => $input->{body},
+  );
+  local $@;
+  eval { sendmail($email) };
+  if ( $@ ) {
+    return $self->render( text => $@->message, status => 500 );
+  } else {
+    return $self->render( text => '' );
+  }
+};
+
+app->start;
+

commit 81e562e6067ccf33c24ab3713163a0eefb1438bd
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Aug 29 13:07:45 2015 -0700

    fix password reset emails based on svc_acct email address, fallout from #25533

diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index 824ff67..6332dd7 100644
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -3032,7 +3032,7 @@ sub reset_passwd {
     my($username, $domain) = split('@', $p->{'email'});
     my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } );
     if ( $svc_domain ) {
-      $svc_acct = qsearchs('svc_acct', { 'username' => $p->{'username'},
+      $svc_acct = qsearchs('svc_acct', { 'username' => $username,
                                          'domsvc'   => $svc_domain->svcnum  }
                           );
       if ( $svc_acct ) {
@@ -3120,7 +3120,7 @@ sub reset_passwd {
 
     my $reset_session = {
       'svcnum'   => $svc_acct->svcnum,
-      'agentnum' =>
+      'agentnum' => $svc_acct->cust_main->agentnum,
     };
 
     my $timeout = '1 hour'; #?

commit 46b9a9665971f30562b0a6a6231561116399d3a0
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Aug 28 15:40:58 2015 -0700

    typo

diff --git a/FS/FS/msg_template/email.pm b/FS/FS/msg_template/email.pm
index 275dc82..f8ebfa0 100644
--- a/FS/FS/msg_template/email.pm
+++ b/FS/FS/msg_template/email.pm
@@ -492,7 +492,7 @@ sub send_prepared {
   }
 
   warn "$me sending message\n" if $DEBUG;
-  my $message = join("\n\n", $cust_msg->header, $cust_msg->body);
+  my $message = join("\n", $cust_msg->header, $cust_msg->body);
   local $@;
   eval {
     sendmail( $message, { transport => $transport,

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/ClientAPI/MyAccount.pm  |    4 +-
 FS/FS/cust_msg.pm             |   19 +++++
 FS/FS/msg_template.pm         |  100 ++++++++++++++++++++++++--
 FS/FS/msg_template/email.pm   |   13 +---
 FS/FS/msg_template/http.pm    |  155 +++++++++++++++++++++++++++++++++++++++++
 bin/msg_template_http-demo.pl |   76 ++++++++++++++++++++
 6 files changed, 351 insertions(+), 16 deletions(-)
 create mode 100644 FS/FS/msg_template/http.pm
 create mode 100755 bin/msg_template_http-demo.pl




More information about the freeside-commits mailing list