agent-virtualize credit card surcharge percentage, RT#72961
[freeside.git] / FS / FS / saved_search.pm
index 075d759..fd82439 100644 (file)
@@ -1,13 +1,15 @@
 package FS::saved_search;
-use base qw( FS::option_Common FS::Record );
+use base qw( FS::Record );
 
 use strict;
 use FS::Record qw( qsearch qsearchs );
 use FS::Conf;
+use FS::Log;
+use FS::Misc qw(send_email);
+use MIME::Entity;
 use Class::Load 'load_class';
 use URI::Escape;
 use DateTime;
-use Try::Tiny;
 
 =head1 NAME
 
@@ -56,6 +58,10 @@ A descriptive name.
 
 The path to the page within the Mason document space.
 
+=item params
+
+The query string for the search.
+
 =item disabled
 
 'Y' to hide the search from the user's Reports / Saved menu.
@@ -128,6 +134,7 @@ sub check {
     #|| $self->ut_foreign_keyn('usernum', 'access_user', 'usernum')
     || $self->ut_text('searchname')
     || $self->ut_text('path')
+    || $self->ut_textn('params') # URL-escaped, so ut_textn
     || $self->ut_flag('disabled')
     || $self->ut_enum('freq', [ '', 'daily', 'weekly', 'monthly' ])
     || $self->ut_numbern('last_sent')
@@ -138,6 +145,14 @@ sub check {
   $self->SUPER::check;
 }
 
+sub replace_check {
+  my ($new, $old) = @_;
+  if ($new->usernum != $old->usernum) {
+    return "can't change owner of a saved search";
+  }
+  '';
+}
+
 =item next_send_date
 
 Returns the next date this report should be sent next. If it's not set for
@@ -168,8 +183,6 @@ Returns the CGI query string for the parameters to this report.
 
 =cut
 
-# multivalued options are newline-separated in the database
-
 sub query_string {
   my $self = shift;
 
@@ -177,12 +190,7 @@ sub query_string {
   $type = 'html-print' if $type eq '' || $type eq 'html';
   $type = '.xls' if $type eq 'xls';
   my $query = "_type=$type";
-  my %options = $self->options;
-  foreach my $k (keys %options) {
-    foreach my $v (split("\n", $options{$k})) {
-      $query .= ';' . uri_escape($k) . '=' . uri_escape($v);
-    }
-  }
+  $query .= ';' . $self->params if $self->params;
   $query;
 }
 
@@ -194,6 +202,7 @@ Returns the report content as an HTML or Excel file.
 
 sub render {
   my $self = shift;
+  my $log = FS::Log->new('FS::saved_search::render');
   my $outbuf;
 
   # delayed loading
@@ -210,11 +219,10 @@ sub render {
 
   local $FS::CurrentUser::CurrentUser = $self->access_user;
   local $FS::Mason::Request::QUERY_STRING = $self->query_string;
-  local $FS::Mason::Request::FSURL = ''; #?
-#  local $ENV{SERVER_NAME} = 'localhost'; #?
-#  local $ENV{SCRIPT_NAME} = '/freeside'. $self->path;
+  local $FS::Mason::Request::FSURL = $self->access_user->option('rooturl');
 
-  my $mason_request = $fs_interp->make_request(comp => $self->path);
+  my $mason_request = $fs_interp->make_request(comp => '/' . $self->path);
+  $mason_request->notes('inline_stylesheet', 1);
 
   local $@;
   eval { $mason_request->exec(); };
@@ -224,9 +232,9 @@ sub render {
       $error = $error->message;
     }
 
-    warn "Error rendering " . $self->path .
+    $log->error("Error rendering " . $self->path .
          " for " . $self->access_user->username .
-         ":\n$error\n";
+         ":\n$error\n");
     # send it to the user anyway, so there's a way to diagnose the error
     $outbuf = '<h3>Error</h3>
   <p>There was an error generating the report "'.$self->searchname.'".</p>
@@ -234,7 +242,75 @@ sub render {
   <p>' . $_ . '</p>';
   }
 
-  return $outbuf;
+  my %mime = (
+    Data        => $outbuf,
+    Type        => $mason_request->notes('header-content-type')
+                   || 'text/html',
+    Disposition => 'inline',
+  );
+  if (my $disp = $mason_request->notes('header-content-disposition') ) {
+    $disp =~ /^(attachment|inline)\s*;\s*filename=(.*)$/;
+    $mime{Disposition} = $1;
+    my $filename = $2;
+    $filename =~ s/^"(.*)"$/$1/;
+    $mime{Filename} = $filename;
+  }
+  if ($mime{Type} =~ /^text/) {
+    $mime{Encoding} = 'quoted-printable';
+  } else {
+    $mime{Encoding} = 'base64';
+  }
+  return MIME::Entity->build(%mime);
+}
+
+=item send
+
+Sends the search by email. If anything fails, logs and returns an error.
+
+=cut
+
+sub send {
+  my $self = shift;
+  my $log = FS::Log->new('FS::saved_search::send');
+  my $conf = FS::Conf->new;
+  my $user = $self->access_user;
+  my $username = $user->username;
+  my $user_email = $user->option('email_address');
+  my $error;
+  if (!$user_email) {
+    $error = "User '$username' has no email address.";
+    $log->error($error);
+    return $error;
+  }
+  $log->debug('Rendering saved search');
+  my $part = $self->render;
+
+  my %email_param = (
+    'from'      => $conf->config('invoice_from'),
+    'to'        => $user_email,
+    'subject'   => $self->searchname,
+    'nobody'    => 1,
+    'mimeparts' => [ $part ],
+  );
+
+  $log->debug('Sending to '.$user_email);
+  $error = send_email(%email_param);
+
+  # update the timestamp
+  $self->set('last_sent', time);
+  $error ||= $self->replace;
+  if ($error) {
+    $log->error($error);
+    return $error;
+  }
+
+}
+
+sub queueable_send {
+  my $searchnum = shift;
+  my $self = FS::saved_search->by_key($searchnum)
+    or die "searchnum $searchnum not found\n";
+  $self->send;
 }
 
 =back