[freeside-commits] branch master updated. 0929e7aec153632691a64ae8755c082090fd1de5

Mark Wells mark at 420.am
Thu Sep 8 11:06:12 PDT 2016


The branch, master has been updated
       via  0929e7aec153632691a64ae8755c082090fd1de5 (commit)
       via  1768c2039b6ff3d68bd3b711621d82dcbf5eab75 (commit)
       via  54ef6b82c54b5d4c6336ab9fe96aed56fc92963c (commit)
       via  2d2662d900652d9b1f242e9affd5a6e67c453f4d (commit)
      from  6df492990ea195513430f3a56d537e57e50b6913 (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 0929e7aec153632691a64ae8755c082090fd1de5
Merge: 1768c20 6df4929
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Sep 8 11:05:38 2016 -0700

    Merge branch 'master' of git.freeside.biz:/home/git/freeside


commit 1768c2039b6ff3d68bd3b711621d82dcbf5eab75
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Sep 8 11:05:26 2016 -0700

    fix UI links and CSS in searches delivered by email, #72101

diff --git a/FS/FS/saved_search.pm b/FS/FS/saved_search.pm
index 252dc71..caaf7fe 100644
--- a/FS/FS/saved_search.pm
+++ b/FS/FS/saved_search.pm
@@ -219,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);
+  $mason_request->notes('inline_stylesheet', 1);
 
   local $@;
   eval { $mason_request->exec(); };
diff --git a/httemplate/edit/saved_search.html b/httemplate/edit/saved_search.html
index 3039aed..cb6aa45 100644
--- a/httemplate/edit/saved_search.html
+++ b/httemplate/edit/saved_search.html
@@ -61,6 +61,28 @@
 <%init>
 
 my $curuser = $FS::CurrentUser::CurrentUser;
+# remember the user's rooturl() when accessing the UI. this will be the
+# base URL for sending email reports to that user so that links work.
+my $rooturl_pref = qsearchs('access_user_pref', {
+  usernum   => $curuser->usernum,
+  prefname  => 'rooturl',
+});
+my $error;
+if ($rooturl_pref) {
+  if ($rooturl_pref->prefvalue ne rooturl()) {
+    $rooturl_pref->set('prefvalue', rooturl());
+    $error = $rooturl_pref->replace;
+  } # else don't update it
+} else {
+  $rooturl_pref = FS::access_user_pref->new({
+    usernum   => $curuser->usernum,
+    prefname  => 'rooturl',
+    prefvalue => rooturl(),
+  });
+  $error = $rooturl_pref->insert;
+}
+
+warn "error updating rooturl pref: $error" if $error;
 
 # prefix to the freeside document root (usually '/freeside/')
 my $root = URI->new($fsurl)->path;
diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html
index 839a636..327673b 100644
--- a/httemplate/elements/header-popup.html
+++ b/httemplate/elements/header-popup.html
@@ -38,7 +38,13 @@ Example:
     <% $head |n %>
   </HEAD>
   <BODY <% $etc |n %>>
+%   if ($m->notes('inline_stylesheet')) { # for email delivery
+    <style type="text/css">
+    <& /elements/freeside.css &>
+    </style>
+%   } else {
     <link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
+%   }
 % if ( $title || $title_noescape ) {
     <FONT SIZE=6>
       <CENTER><% encode_entities($title) || $title_noescape |n %></CENTER>

commit 54ef6b82c54b5d4c6336ab9fe96aed56fc92963c
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Sep 8 10:26:23 2016 -0700

    add UI to manage saved searches, #72101

diff --git a/httemplate/browse/saved_search.html b/httemplate/browse/saved_search.html
new file mode 100644
index 0000000..d2efa6e
--- /dev/null
+++ b/httemplate/browse/saved_search.html
@@ -0,0 +1,81 @@
+<& elements/browse.html,
+  'title'       => 'My saved searches',
+  'name'        => 'saved searches',
+  'query'       => { 'table'     => 'saved_search',
+                     'hashref'   => { usernum => $curuser->usernum },
+                   },
+  'count_query' => $count_query,
+  'header'      => [ '#',
+                     'Name',
+                     'Subscription',
+                     'Last sent',
+                     'Format',
+                     'Path',
+                     'Parameters',
+                   ],
+  'sort_fields' => [ 'searchnum',
+                     'searchname',
+                     'freq',
+                     'last_sent',
+                     'format',
+                     "path || '?' || 'params'",
+                     '',
+                   ],
+  'fields'      => [ 'searchnum',
+                     'searchname',
+                     'freq',
+                     sub { my $date = shift->get('last_sent');
+                           $date ? time2str('%b %o, %Y', $date) : '';
+                     },
+                     sub { $format_label{ shift->get('format') }
+                     },
+                     'path',
+                     sub { join('<BR>',
+                             sort
+                             map { encode_entities(uri_unescape($_)) }
+                             split(/[;&]/, shift->get('params') )
+                           )
+                     },
+                   ],
+  'size'        => [ '',
+                     '',
+                     '',
+                     '',
+                     '',
+                     '',
+                     '-1',
+                   ],
+  'links'                   => [ '', '' ],
+  'link_onclicks'           => [ '', $edit_popup ],
+#  'disableable'             => 1, # currrently unused
+#  'disabled_statuspos'      => 2,
+  'really_disable_download' => 1
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $query = {
+  'table'   => 'saved_search',
+  'hashref' => { 'usernum' => $curuser->usernum },
+};
+my $count_query = "SELECT COUNT(*) FROM saved_search WHERE usernum = ".
+  $curuser->usernum;
+
+my %format_label = (
+  'html' => 'webpage',
+  'csv'  => 'CSV',
+  'xls'  => 'spreadsheet',
+);
+
+my $edit_popup = sub {
+  my $searchnum = shift->searchnum;
+  include('/elements/popup_link_onclick.html',
+    'action'        => $fsurl.'/edit/saved_search.html?'.$searchnum,
+    'actionlabel'   => 'Save this search',
+    'width'         => 650,
+    'height'        => 500,
+  );
+};
+
+</%init>
diff --git a/httemplate/edit/process/saved_search.html b/httemplate/edit/process/saved_search.html
new file mode 100644
index 0000000..7ae7e0d
--- /dev/null
+++ b/httemplate/edit/process/saved_search.html
@@ -0,0 +1,15 @@
+<& elements/process.html,
+  'table'         => 'saved_search',
+  'popup_reload'  => 'Saving',
+  'post_new_object_callback' => $callback,
+&>
+<%init>
+
+my $callback = sub {
+  my ($cgi, $obj) = @_;
+  $obj->usernum( $FS::CurrentUser::CurrentUser->usernum );
+  # if this would change it from its existing owner, replace_check
+  # will refuse
+};
+
+</%init>
diff --git a/httemplate/edit/saved_search.html b/httemplate/edit/saved_search.html
new file mode 100644
index 0000000..3039aed
--- /dev/null
+++ b/httemplate/edit/saved_search.html
@@ -0,0 +1,91 @@
+<& elements/edit.html,
+  'name'   => 'saved search',
+  'table'  => 'saved_search',
+  'popup'  => 1,
+  'fields' => [
+    { field   => 'searchname',
+      type    => 'text',
+      size    => 40,
+    },
+    { field   => 'freq',
+      type    => 'select',
+      options => [ '', 'daily', 'weekly', 'monthly' ],
+      labels  => { '' => 'no' },
+    },
+    { field   => 'emailaddress',
+      type    => 'fixed',
+      curr_value_callback => sub {
+        $curuser->option('email_address')
+        || 'no email address configured'
+      },
+    },
+    { field   => 'last_sent',
+      type    => 'fixed-date',
+    },
+    { field   => 'format',
+      type    => 'hidden', # revisit this later
+#      type    => 'select',
+#      options => [ 'html', 'xls', 'csv' ],
+#      labels => {
+#        'html' => 'webpage',
+#        'xls'  => 'spreadsheet',
+#        'csv'  => 'CSV',
+#      },
+    },
+    { field => 'disabled', # currently unused
+      type  => 'hidden',
+    },
+    { type  => 'tablebreak-tr-title' },
+    { field => 'path',
+      type  => 'fixed',
+      cell_style => 'font-size: small',
+    },
+    { field => 'params',
+      type  => 'fixed',
+      cell_style => 'font-size: small',
+    },
+  ],
+  'labels' => {
+    'searchnum'         => 'Saved search',
+    'searchname'        => 'Name this search',
+    'path'              => 'Search page',
+    'params'            => 'Parameters',
+    'freq'              => 'Subscribe by email',
+    'last_sent'         => 'Last sent on',
+    'emailaddress'      => 'Will be sent to',
+    'format'            => 'Report format',
+  },
+  'new_object_callback' => $new_object,
+  'delete_url'          => $fsurl.'misc/delete-saved_search.html',
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+# prefix to the freeside document root (usually '/freeside/')
+my $root = URI->new($fsurl)->path;
+
+# alternatively, could do all this on the client using window.top.location
+my $new_object = sub {
+  my $cgi = shift;
+  my $hashref = shift;
+  my $fields = shift;
+  for (grep { $_->{field} eq 'last_sent' } @$fields) {
+    $_->{type} = 'hidden';
+  }
+  my $url = $r->header_in('Referer')
+    or die "no referring page found";
+  $url = URI->new($url);
+  my $path = $url->path;
+  $path =~ s/^$root//; # path should not have a leading slash
+  my $title = $cgi->param('title');
+  return FS::saved_search->new({
+    'usernum'     => $curuser->usernum,
+    'path'        => $path,
+    'params'      => $url->query,
+    'format'      => 'html',
+    'searchname'  => $title,
+  });
+};
+
+</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index fcfc9fb..d6ea068 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -87,6 +87,21 @@ my $mobile = $opt{'mobile'} || 0;
 
 my $curuser = $FS::CurrentUser::CurrentUser;
 
+# saved searches
+tie my %report_saved_searches, 'Tie::IxHash';
+if ( my @searches = grep { $_->disabled eq '' } $curuser->saved_search ) {
+  foreach my $search (@searches) {
+    $report_saved_searches{ $search->searchname } = [
+      # don't use query_string here; we don't want to override the format
+      $fsurl . $search->path . '?' . $search->params , ''
+    ];
+  }
+  $report_saved_searches{'separator'} = '';
+  $report_saved_searches{'My saved searches'} =
+    [ $fsurl. 'browse/saved_search.html',
+      'Manage saved searches and subscriptions' ];
+}
+
 #XXX Active tickets not assigned to a customer
 
 tie my %report_prospects, 'Tie::IxHash';
@@ -419,6 +434,8 @@ $report_logs{'Outgoing messages'} = [ $fsurl.'search/cust_msg.html', 'View outgo
   || $curuser->access_right('Configuration');
 
 tie my %report_menu, 'Tie::IxHash';
+$report_menu{'Saved searches'} = [ \%report_saved_searches, 'My saved searches' ]
+  if keys(%report_saved_searches);
 $report_menu{'Prospects'}      = [ \%report_prospects, 'Prospect reports' ]
   if $curuser->access_right('List prospects')
   || $curuser->access_right('List contacts');
diff --git a/httemplate/elements/tr-fixed-date.html b/httemplate/elements/tr-fixed-date.html
index ef59979..731a3ca 100644
--- a/httemplate/elements/tr-fixed-date.html
+++ b/httemplate/elements/tr-fixed-date.html
@@ -14,6 +14,6 @@ my $value = $opt{'curr_value'} || $opt{'value'};
 my $conf = new FS::Conf;
 my $date_format = $opt{'format'} || $conf->config('date_format') || '%m/%d/%Y';
 
-$opt{'formatted_value'} = time2str($date_format, $value);
+$opt{'formatted_value'} = $value > 0 ? time2str($date_format, $value) : '';
 
 </%init>
diff --git a/httemplate/misc/delete-saved_search.html b/httemplate/misc/delete-saved_search.html
new file mode 100644
index 0000000..34567ec
--- /dev/null
+++ b/httemplate/misc/delete-saved_search.html
@@ -0,0 +1,25 @@
+% if ( $error ) {
+<& /elements/errorpage-popup.html, $error &>
+% } else {
+<& /elements/header-popup.html, 'Saved search deleted' &>
+  <script type="text/javascript">
+  topreload();
+  </script>
+</body>
+</html>
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal searchnum";
+my $searchnum = $1;
+
+my $search = qsearchs('saved_search', {
+  'searchnum' => $searchnum,
+  'usernum'   => $curuser->usernum,
+});
+my $error = $search->delete;
+
+</%init>
diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html
index 12f6c1e..3ea38ae 100644
--- a/httemplate/search/elements/search-html.html
+++ b/httemplate/search/elements/search-html.html
@@ -136,22 +136,36 @@
 
               <TD ALIGN="right" CLASS="noprint">
 
-                <% $opt{'download_label'} || 'Download full results' %><BR>
+                <% $opt{'download_label'} || 'Download results:' %>
 
 %               $cgi->param('_type', "$xlsname.xls" ); 
-                as <A HREF="<% "$self_url?". $cgi->query_string %>">Excel spreadsheet</A><BR>
+                <A HREF="<% "$self_url?". $cgi->query_string %>">Spreadsheet</A> | 
 
 %               $cgi->param('_type', 'csv'); 
-                as <A HREF="<% "$self_url?". $cgi->query_string %>">CSV file</A><BR>
+                <A HREF="<% "$self_url?". $cgi->query_string %>">CSV</A> | 
 
 %             if ( defined($opt{xml_elements}) ) {
 %               $cgi->param('_type', 'xml'); 
-                as <A HREF="<% "$self_url?". $cgi->query_string %>">XML file</A><BR>
+                <A HREF="<% "$self_url?". $cgi->query_string %>">XML</A> | 
 %             }
 
 %               $cgi->param('_type', 'html-print'); 
-                as <A HREF="<% "$self_url?". $cgi->query_string %>">printable copy</A>
+                <A HREF="<% "$self_url?". $cgi->query_string %>">webpage</A>
 
+%# "save search" -- for now, obey disable_download and the 'Download
+%# report data' ACL, because saving a search allows the user to receive
+%# copies of the data.
+                <BR>
+%# XXX should do a check here on whether the user already has this
+%# search saved...
+                <& /elements/popup_link.html,
+                  'action'        => $fsurl.'/edit/saved_search.html?title='.
+                                       uri_escape($opt{title}),
+                  'label'         => 'Save this search',
+                  'actionlabel'   => 'Save this search',
+                  'width'         => 650,
+                  'height'        => 500,
+                &>
               </TD>
 %             $cgi->param('_type', "html" ); 
 %           } 
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
index b6ee7b3..0f71218 100644
--- a/httemplate/search/elements/search.html
+++ b/httemplate/search/elements/search.html
@@ -179,6 +179,7 @@ Example:
   &>
 
 </%doc>
+% # if changing this, also update saved search behavior to match!
 % if ( $type eq 'csv' ) {
 %
 <% include('search-csv.html',  header=>$header, rows=>$rows, opt=>\%opt ) %>

commit 2d2662d900652d9b1f242e9affd5a6e67c453f4d
Author: Mark Wells <mark at freeside.biz>
Date:   Wed Sep 7 15:08:14 2016 -0700

    add email delivery of saved searches, #72101

diff --git a/FS/FS/Cron/send_subscribed.pm b/FS/FS/Cron/send_subscribed.pm
new file mode 100644
index 0000000..2b1f662
--- /dev/null
+++ b/FS/FS/Cron/send_subscribed.pm
@@ -0,0 +1,32 @@
+package FS::Cron::send_subscribed;
+
+use strict;
+use base 'Exporter';
+use FS::saved_search;
+use FS::Record qw(qsearch);
+use FS::queue;
+
+our @EXPORT_OK = qw( send_subscribed );
+our $DEBUG = 1;
+
+sub send_subscribed {
+
+  my @subs = qsearch('saved_search', {
+    'disabled'  => '',
+    'freq'      => { op => '!=', value => '' },
+  });
+  foreach my $saved_search (@subs) {
+    my $date = $saved_search->next_send_date;
+    warn "checking '".$saved_search->searchname."' with date $date\n"
+      if $DEBUG;
+    
+    if ( $^T > $saved_search->next_send_date ) {
+      warn "queueing delivery\n";
+      my $job = FS::queue->new({ job => 'FS::saved_search::queueable_send' });
+      $job->insert( $saved_search->searchnum );
+    }
+  }
+
+}
+
+1;
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index e8a1af6..ee87b2d 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -416,7 +416,6 @@ if ( -e $addl_handler_use_file ) {
   use FS::commission_schedule;
   use FS::commission_rate;
   use FS::saved_search;
-  use FS::saved_search_option;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 4ff9db2..df987ff 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -7492,6 +7492,7 @@ sub tables_hashref {
         'usernum',      'int',     'NULL',      '', '', '',
         'searchname',   'varchar', '',     $char_d, '', '',
         'path',         'varchar', '',     $char_d, '', '',
+        'params',       'text',    'NULL',      '', '', '',
         'disabled',     'char',    'NULL',       1, '', '',
         'freq',         'varchar', 'NULL',      16, '', '',
         'last_sent',    'int',     'NULL',      '', '', '',
@@ -7507,23 +7508,6 @@ sub tables_hashref {
                          ],
     },
 
-    'saved_search_option' => {
-      'columns' => [
-        'optionnum',  'serial',  '',          '', '', '',
-        'searchnum',  'int',     '',          '', '', '',
-        'optionname', 'varchar', '', $char_d, '', '', 
-        'optionvalue', 'text', 'NULL', '', '', '', 
-      ],
-      'primary_key'   => 'optionnum',
-      'unique'        => [ [ 'searchnum', 'optionname' ] ],
-      'index'         => [],
-      'foreign_keys'  => [
-                           { columns => [ 'searchnum' ],
-                             table   => 'saved_search',
-                           },
-                         ],
-    },
- 
     # name type nullability length default local
 
     #'new_table' => {
diff --git a/FS/FS/log_context.pm b/FS/FS/log_context.pm
index 37befb5..ee3e413 100644
--- a/FS/FS/log_context.pm
+++ b/FS/FS/log_context.pm
@@ -13,6 +13,8 @@ my @contexts = ( qw(
   FS::pay_batch::import_from_gateway
   FS::part_pkg
   FS::Misc::Geo::standardize_uscensus
+  FS::saved_search::send
+  FS::saved_search::render
   Cron::bill
   Cron::backup
   Cron::upload
diff --git a/FS/FS/saved_search.pm b/FS/FS/saved_search.pm
index 075d759..252dc71 100644
--- a/FS/FS/saved_search.pm
+++ b/FS/FS/saved_search.pm
@@ -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
@@ -214,7 +223,7 @@ sub render {
 #  local $ENV{SERVER_NAME} = 'localhost'; #?
 #  local $ENV{SCRIPT_NAME} = '/freeside'. $self->path;
 
-  my $mason_request = $fs_interp->make_request(comp => $self->path);
+  my $mason_request = $fs_interp->make_request(comp => '/' . $self->path);
 
   local $@;
   eval { $mason_request->exec(); };
@@ -224,9 +233,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>
@@ -237,6 +246,63 @@ sub render {
   return $outbuf;
 }
 
+=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 $content = $self->render;
+  # XXX come back to this for content-type options
+  my $part = MIME::Entity->build(
+    'Type'        => 'text/html',
+    'Encoding'    => 'quoted-printable', # change this for spreadsheet
+    'Disposition' => 'inline',
+    'Data'        => $content,
+  );
+
+  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
 
 =head1 SEE ALSO
diff --git a/FS/FS/saved_search_option.pm b/FS/FS/saved_search_option.pm
deleted file mode 100644
index f349af3..0000000
--- a/FS/FS/saved_search_option.pm
+++ /dev/null
@@ -1,124 +0,0 @@
-package FS::saved_search_option;
-use base qw( FS::Record );
-
-use strict;
-use FS::Record qw( qsearch qsearchs );
-
-=head1 NAME
-
-FS::saved_search_option - Object methods for saved_search_option records
-
-=head1 SYNOPSIS
-
-  use FS::saved_search_option;
-
-  $record = new FS::saved_search_option \%hash;
-  $record = new FS::saved_search_option { 'column' => 'value' };
-
-  $error = $record->insert;
-
-  $error = $new_record->replace($old_record);
-
-  $error = $record->delete;
-
-  $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::saved_search_option object represents a CGI parameter for a report
-saved in L<FS::saved_search>.  FS::saved_search_option inherits from
-FS::Record.  The following fields are currently supported:
-
-=over 4
-
-=item optionnum
-
-primary key
-
-=item searchnum
-
-searchnum
-
-=item optionname
-
-optionname
-
-=item optionvalue
-
-optionvalue
-
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new parameter.  To add the record to the database, see
-L<"insert">.
-
-Note that this stores the hash reference, not a distinct copy of the hash it
-points to.  You can ask the object for a copy with the I<hash> method.
-
-=cut
-
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'saved_search_option'; }
-
-=item insert
-
-Adds this record to the database.  If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Delete this record from the database.
-
-=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.
-
-=item check
-
-Checks all fields to make sure this is a valid example.  If there is
-an error, returns the error, otherwise returns false.  Called by the insert
-and replace methods.
-
-=cut
-
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
-sub check {
-  my $self = shift;
-
-# unpack these from the format used by CGI
-  my $optionvalue = $self->optionvalue;
-  $optionvalue =~ s/\0/\n/g;
-
-  my $error = 
-    $self->ut_numbern('optionnum')
-    || $self->ut_number('searchnum')
-#   || $self->ut_foreign_key('searchnum', 'saved_search', 'searchnum')
-    || $self->ut_text('optionname')
-    || $self->ut_textn('optionvalue')
-  ;
-  return $error if $error;
-
-  $self->SUPER::check;
-}
-
-=back
-
-=head1 SEE ALSO
-
-L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/MANIFEST b/FS/MANIFEST
index d06f263..73a740f 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -876,5 +876,3 @@ FS/commission_rate.pm
 t/commission_rate.t
 FS/saved_search.pm
 t/saved_search.t
-FS/saved_search_option.pm
-t/saved_search_option.t
diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily
index 6a2daf9..03d2350 100755
--- a/FS/bin/freeside-daily
+++ b/FS/bin/freeside-daily
@@ -79,6 +79,10 @@ pay_batch_receive(%opt);
 use FS::Cron::export_batch qw(export_batch_submit);
 export_batch_submit(%opt);
 
+#does nothing unless there are users with subscribed searches
+use FS::Cron::send_subscribed qw(send_subscribed);
+send_subscribed(%opt);
+
 #clears out cacti imports & deletes select database cache files
 use FS::Cron::cleanup qw( cleanup cleanup_before_backup );
 cleanup_before_backup();
diff --git a/FS/t/saved_search_option.t b/FS/t/saved_search_option.t
deleted file mode 100644
index f30bfb8..0000000
--- a/FS/t/saved_search_option.t
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN { $| = 1; print "1..1\n" }
-END {print "not ok 1\n" unless $loaded;}
-use FS::saved_search_option;
-$loaded=1;
-print "ok 1\n";

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

Summary of changes:
 FS/FS/Cron/send_subscribed.pm               |   32 +++++++
 FS/FS/Mason.pm                              |    1 -
 FS/FS/Schema.pm                             |   18 +---
 FS/FS/log_context.pm                        |    2 +
 FS/FS/saved_search.pm                       |   97 +++++++++++++++++----
 FS/FS/saved_search_option.pm                |  124 ---------------------------
 FS/MANIFEST                                 |    2 -
 FS/bin/freeside-daily                       |    4 +
 FS/t/saved_search_option.t                  |    5 --
 httemplate/browse/saved_search.html         |   81 +++++++++++++++++
 httemplate/edit/process/saved_search.html   |   15 ++++
 httemplate/edit/saved_search.html           |  113 ++++++++++++++++++++++++
 httemplate/elements/header-popup.html       |    6 ++
 httemplate/elements/menu.html               |   17 ++++
 httemplate/elements/tr-fixed-date.html      |    2 +-
 httemplate/misc/delete-saved_search.html    |   25 ++++++
 httemplate/search/elements/search-html.html |   24 ++++--
 httemplate/search/elements/search.html      |    1 +
 18 files changed, 398 insertions(+), 171 deletions(-)
 create mode 100644 FS/FS/Cron/send_subscribed.pm
 delete mode 100644 FS/FS/saved_search_option.pm
 delete mode 100644 FS/t/saved_search_option.t
 create mode 100644 httemplate/browse/saved_search.html
 create mode 100644 httemplate/edit/process/saved_search.html
 create mode 100644 httemplate/edit/saved_search.html
 create mode 100644 httemplate/misc/delete-saved_search.html




More information about the freeside-commits mailing list