Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE...
authorIvan Kohler <ivan@freeside.biz>
Sun, 28 Oct 2018 20:23:32 +0000 (13:23 -0700)
committerIvan Kohler <ivan@freeside.biz>
Sun, 28 Oct 2018 20:23:32 +0000 (13:23 -0700)
16 files changed:
FS/FS/ClientAPI/MyAccount.pm
FS/FS/IP_Mixin.pm
FS/FS/Record.pm
FS/FS/Schema.pm
FS/FS/cust_main/Search.pm
FS/FS/log.pm
FS/FS/log_email.pm
FS/FS/part_event/Action/bill_sales_credit.pm
httemplate/browse/discount.html
httemplate/browse/log_email.html
httemplate/edit/log_email.html
httemplate/edit/msg_template.html
httemplate/misc/delete-log_email.html
httemplate/search/cust_bill_pkg_discount.html
httemplate/search/report_cust_main.html
httemplate/view/prospect_main.html

index ba698bd..9e1a4ad 100644 (file)
@@ -1016,9 +1016,6 @@ sub validate_payment {
     my $payinfo2 = $1;
     $payinfo = $payinfo1. '@'. $payinfo2;
 
-    $payinfo = $cust_main->payinfo
-      if $cust_main->paymask eq $payinfo;
-
     my $achonfile = 0;
       if ( $cust_main->paymask eq $payinfo ) {
         $payinfo = $cust_main->payinfo;
@@ -1655,128 +1652,6 @@ sub payment_receipt {
          };
 }
 
-sub list_payby {
-  my $p = shift;
-
-  my($context, $session, $custnum) = _custoragent_session_custnum($p);
-  return { 'error' => $session } if $context eq 'error';
-
-  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
-    or return { 'error' => "unknown custnum $custnum" };
-
-  return {
-    'payby' => [ map {
-                       my $cust_payby = $_;
-                       +{
-                          map { $_ => $cust_payby->$_ }
-                            qw( custpaybynum weight payby paymask paydate
-                                payname paystate paytype
-                              )
-                        };
-                     }
-                   $cust_main->cust_payby
-               ],
-  };
-}
-
-sub insert_payby {
-  my $p = shift;
-
-  my($context, $session, $custnum) = _custoragent_session_custnum($p);
-  return { 'error' => $session } if $context eq 'error';
-
-  #XXX payinfo1 + payinfo2 for CHEK?
-  #or take the opportunity to use separate, more well- named fields?
-  # my $payinfo;
-  # $p->{'payinfo1'} =~ /^([\dx]+)$/
-  #   or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
-  # my $payinfo1 = $1;
-  #  $p->{'payinfo2'} =~ /^([\dx\.]+)$/ # . turned on by echeck-country CA ?
-  #   or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
-  # my $payinfo2 = $1;
-  # $payinfo = $payinfo1. '@'. $payinfo2;
-
-  my $cust_payby = new FS::cust_payby {
-    'custnum' => $custnum,
-    map { $_ => $p->{$_} } qw( weight payby payinfo paycvv paydate payname
-                               paystate paytype payip 
-                             ),
-  };
-
-  my $error = $cust_payby->insert;
-  if ( $error ) {
-    return { 'error' => $error };
-  } else {
-    return { 'custpaybynum' => $cust_payby->custpaybynum };
-  }
-  
-}
-
-sub update_payby {
-  my $p = shift;
-
-  my($context, $session, $custnum) = _custoragent_session_custnum($p);
-  return { 'error' => $session } if $context eq 'error';
-
-  my $cust_payby = qsearchs('cust_payby', {
-                              'custnum'      => $custnum,
-                              'custpaybynum' => $p->{'custpaybynum'},
-                           })
-    or return { 'error' => 'unknown custpaybynum '. $p->{'custpaybynum'} };
-
-  foreach my $field (
-    qw( weight payby payinfo paycvv paydate payname paystate paytype payip )
-  ) {
-    next unless exists($p->{$field});
-    $cust_payby->set($field,$p->{$field});
-  }
-
-  my $error = $cust_payby->replace;
-  if ( $error ) {
-    return { 'error' => $error };
-  } else {
-    return { 'custpaybynum' => $cust_payby->custpaybynum };
-  }
-  
-}
-
-sub verify_payby {
-  my $p = shift;
-
-  my($context, $session, $custnum) = _custoragent_session_custnum($p);
-  return { 'error' => $session } if $context eq 'error';
-
-  my $cust_payby = qsearchs('cust_payby', {
-                              'custnum'      => $custnum,
-                              'custpaybynum' => $p->{'custpaybynum'},
-                           })
-    or return { 'error' => 'unknown custpaybynum '. $p->{'custpaybynum'} };
-
-  return { 'error' => $cust_payby->verify };
-  
-}
-
-sub delete_payby {
-  my $p = shift;
-
-  my($context, $session, $custnum) = _custoragent_session_custnum($p);
-  return { 'error' => $session } if $context eq 'error';
-
-  my $cust_payby = qsearchs('cust_payby', {
-                              'custnum'      => $custnum,
-                              'custpaybynum' => $p->{'custpaybynum'},
-                           })
-    or return { 'error' => 'unknown custpaybynum '. $p->{'custpaybynum'} };
-
-  my $conf = new FS::Conf;
-  if (($cust_payby->payby eq "DCHK" || $cust_payby->payby eq "CHEK") && $conf->exists('selfservice-ACH_info_readonly')) {
-    return { 'error' => "Sorry you do not have permission to delete bank information." };
-  }
-  else {
-    return { 'error' => $cust_payby->delete };
-  }
-}
-
 sub cancel {
   my $p = shift;
   my $session = _cache->get($p->{'session_id'})
index 3ec7693..8920ceb 100644 (file)
@@ -94,6 +94,10 @@ sub ip_check {
     $self->ip_addr('');
   }
 
+  # Will strip extraneous leading zeros from ip adddresses
+  # e.g. 10.0.022.220 corrected to 10.0.22.220
+  $self->ut_ip46n('ip_addr');
+
   if ( $self->ip_addr
        and !$self->router
        and $self->conf->exists('auto_router') ) {
index 89957cb..fe8fad9 100644 (file)
@@ -289,6 +289,11 @@ the individual PARAMS_HASHREF queries
 #regular FS::TABLE methods
 #on it.
 
+C<$FS::Record::qsearch_qualify_columns> package global is disabled by default.
+When enabled, the WHERE clause generated from the 'hashref' parameter has
+the table name prepended to each column name. WHERE column = 'value' becomes
+WHERE table.coumn = 'value'
+
 =cut
 
 my %TYPE = (); #for debugging
@@ -2671,7 +2676,7 @@ sub ut_ip {
   $self->getfield($field) =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
     or return "Illegal (IP address) $field: ". $self->getfield($field);
   for ( $1, $2, $3, $4 ) { return "Illegal (IP address) $field" if $_ > 255; }
-  $self->setfield($field, "$1.$2.$3.$4");
+  $self->setfield( $field, $self->_ut_ip_strip_leading_zeros( "$1.$2.$3.$4" ));
   '';
 }
 
@@ -2700,8 +2705,9 @@ Check/untaint IPv4 or IPv6 address.
 
 sub ut_ip46 {
   my( $self, $field ) = @_;
-  my $ip = NetAddr::IP->new($self->getfield($field))
-    or return "Illegal (IP address) $field: ".$self->getfield($field);
+  my $ip = NetAddr::IP->new(
+    $self->_ut_ip_strip_leading_zeros( $self->getfield($field) )
+  ) or return "Illegal (IP address) $field: ".$self->getfield($field);
   $self->setfield($field, lc($ip->addr));
   return '';
 }
@@ -2721,6 +2727,20 @@ sub ut_ip46n {
   $self->ut_ip46($field);
 }
 
+sub _ut_ip_strip_leading_zeros {
+  # strip user-entered leading 0's from IP addresses
+  # so parsers like NetAddr::IP don't mangle the address
+  # e.g. NetAddr::IP converts 10.0.022.220 into 10.0.18.220
+
+  my ( $self, $ip ) = @_;
+
+  return join '.', map int, split /\./, $ip
+    if $ip
+    && $ip =~ /\./
+    && $ip =~ /[\.^]0/;
+  $ip;
+}
+
 =item ut_coord COLUMN [ LOWER [ UPPER ] ]
 
 Check/untaint coordinates.
index ecd071e..2168c4c 100644 (file)
@@ -4665,6 +4665,7 @@ sub tables_hashref {
         'min_level', 'int',  'NULL', '', '', '',
         'msgnum', 'int', '',  '', '', '',
         'to_addr', 'varchar', 'NULL',     255, '', '',
+        'context_height',  'int', 'NULL', '', '', '', 
       ],
       'primary_key'  => 'logemailnum',
       'unique'       => [],
index 1a19ea3..58bdd88 100644 (file)
@@ -1049,8 +1049,9 @@ sub search {
 
     if ( @tagnums ) {
       if ( $params->{'all_tags'} ) {
+        my $exists = $params->{'all_tags'} eq 'all' ? 'exists' : 'not exists';
         foreach ( @tagnums ) {
-          push @where, 'exists(select 1 from cust_tag where '.
+          push @where, $exists.'(select 1 from cust_tag where '.
                        'cust_tag.custnum = cust_main.custnum and tagnum = '.
                        $_ . ')';
         }
index 75e17c8..64d036e 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 use base qw( FS::Record );
 use FS::Record qw( qsearch qsearchs dbdef );
 use FS::UID qw( dbh driver_name );
+use FS::Log;
 use FS::log_context;
 use FS::log_email;
 use FS::upgrade_journal;
@@ -81,18 +82,22 @@ Will send emails according to the conditions in L<FS::log_email>.
 sub insert {
   # not using process_o2m for this, because we don't have a web interface
   my $self = shift;
+
   my $error = $self->SUPER::insert;
   return $error if $error;
-  my $contexts = {}; #for quick checks when sending emails
-  foreach ( @_ ) {
+
+  my $contexts = {};
+  my $context_height = @_;
+  foreach ( @_ ) { # ordered from least to most specific
     my $context = FS::log_context->new({
         'lognum'  => $self->lognum,
         'context' => $_
     });
     $error = $context->insert;
     return $error if $error;
-    $contexts->{$_} = 1;
+    $contexts->{$_} = $context_height--;
   }
+
   foreach my $log_email (
     qsearch('log_email',
       {
@@ -104,19 +109,20 @@ sub insert {
       }
     )
   ) {
-    # shouldn't be a lot of these, so not packing this into the qsearch
+    # shouldn't be a lot of log_email records, so not packing these checks into the qsearch
     next if $log_email->context && !$contexts->{$log_email->context};
+    next if $log_email->context_height && ($contexts->{$log_email->context} > $log_email->context_height);
     my $msg_template = qsearchs('msg_template',{ 'msgnum' => $log_email->msgnum });
     unless ($msg_template) {
       warn "Could not send email when logging, could not load message template for logemailnum " . $log_email->logemailnum;
       next;
     }
     my $emailerror = $msg_template->send(
-      'msgtype'       => 'admin',
-      'to'            => $log_email->to_addr,
+      'msgtype' => 'admin',
+      'to'      => $log_email->to_addr,
       'substitutions' => {
-        'loglevel'   => $FS::Log::LEVELS{$self->level}, # which has hopefully been loaded...
-        'logcontext' => $log_email->context, # use the one that triggered the email
+        'loglevel'   => $FS::Log::LEVELS{$self->level} || 'unknown',
+        'logcontext' => join(', ', keys( %$contexts )) || 'unknown',
         'logmessage' => $self->message,
       },
     );
index 9c53c23..a055cb4 100644 (file)
@@ -42,6 +42,9 @@ The following fields are currently supported:
 
 =item to_addr - who the email will be sent to (in addition to any bcc on the template)
 
+=item context_height - number of context stack levels to match against 
+(0 or null matches against full stack, 1 only matches lowest level context, 2 matches lowest two levels, etc.)
+
 =back
 
 =head1 METHODS
@@ -88,6 +91,7 @@ sub check {
     || $self->ut_number('min_level')
     || $self->ut_foreign_key('msgnum', 'msg_template', 'msgnum')
     || $self->ut_textn('to_addr')
+    || $self->ut_numbern('context_height')
   ;
   return $error if $error;
 
index ab69375..e147430 100644 (file)
@@ -72,7 +72,7 @@ sub do_action {
 
     my $reasonnum = $self->option('reasonnum');
 
-    my $desc = 'from invoice #'. $cust_bill->display_invnum .
+    my $desc = ' for customer #'.$cust_main->display_custnum.': '.$cust_main->name.' from invoice #'. $cust_bill->display_invnum .
                ' ('. time2str($date_format, $cust_bill->_date) . ')';
                # could also show custnum and pkgnums here?
     my $error = $sales_cust_main->credit(
index d3cf873..5ed43cf 100644 (file)
@@ -1,10 +1,9 @@
 <% include( 'elements/browse.html',
                  'title'       => 'Discounts',
                  'name'        => 'discounts',
-                 'menubar'     => [ 'Add a new discount' =>
-                                      $p.'edit/discount.html',
-                                  ],
-                 'query'       => { 'table' => 'discount', },
+                 'menubar'     => \@menubar,
+                 'query'       => \%query,
+                 'order_by_sql' => { description => 'discountnum' },
                  'count_query' => 'SELECT COUNT(*) FROM discount',
                  'disableable' => 1,
                  'disabled_statuspos' => 1,
@@ -13,9 +12,7 @@
                                     'classname',
                                     'description',
                                   ],
-                 'links'       => [ $link,
-                                    $link,
-                                  ],
+                 'links'       => \@links
              )
 %>
 <%init>
 die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
 
-my $link = [ "${p}edit/discount.html?", 'discountnum' ];
+my @links = (
+  [ "${p}edit/discount.html?", 'discountnum' ],
+  [ "${p}edit/discount_class.html?", 'classnum' ],
+);
+
+# Fixes disableable, because discount and discount_class tables
+# both contain a 'disabled' column
+local $FS::Record::qsearch_qualify_columns = 1;
+
+my %query = (
+  select => 'discount.*, discount_class.*',
+  table => 'discount',
+  addl_from => 'LEFT JOIN discount_class USING(classnum)',
+);
+
+my @menubar = (
+  'Add a new discount' => $p.'edit/discount.html',
+  'Discount classes' => $p.'browse/discount_class.html',
+);
 
 </%init>
index 007ea6f..fe583dc 100644 (file)
@@ -6,10 +6,7 @@
                         . $add_condition_link
                         . ' | '
                         . $system_log_link
-                        . '</P>'
-                        . '<SCRIPT>'
-                        . $areyousure
-                        . '</SCRIPT>',
+                        . '</P>',
      'query'         => $query,
      'count_query'   => $count_query,
      'header'      => [ '#',
 
    ) %>
 
+<script>
+  function areyousure_delete_log_email(logemailnum) {
+    if ( confirm( 'Delete log email condition #' + logemailnum )) {
+      <%
+        include(
+          '/elements/popup_link_onclick.html' => {
+            js_action   => qq( '${fsurl}/misc/delete-log_email.html?logemailnum=' + logemailnum ),
+            actionlabel => 'Delete log email condition',
+            nofalse     => 1,
+          }
+        )
+      %>
+      return;
+    }
+  }
+</script>
+
 <%init>
 
 my $curuser = $FS::CurrentUser::CurrentUser;
@@ -50,11 +64,7 @@ my $curuser = $FS::CurrentUser::CurrentUser;
 die "access denied"
   unless $curuser->access_right([ 'View system logs', 'Configuration' ]);
 
-my $add_condition_link = include('/elements/popup_link.html',
-  'action' => $p.'edit/log_email.html?popup=1',
-  'label'  => 'Add log email condition',
-  'actionlabel' => 'Add log email condition',
-);
+my $add_condition_link = qq( <a href="${fsurl}edit/log_email.html">Add log email condition</a> );
 
 my $system_log_link = qq(<A HREF="${p}search/log.html">System Log</A>);
 
@@ -68,24 +78,10 @@ my $query = {
 my $count_query = "SELECT COUNT(*) FROM log_email";
 
 my $actions = sub {
-  my $log_email = shift;
-  my $logemailnum = $log_email->logemailnum;
+  my $logemailnum = shift->logemailnum;
   qq!<A HREF="javascript:areyousure_delete_log_email($logemailnum)">(delete)</A>!;
 };
 
-my $areyousure_onclick = include('/elements/popup_link_onclick.html',
-  'js_action' => q(') . $p . q(misc/delete-log_email.html?logemailnum=' + logemailnum),
-  'actionlabel' => 'Delete log email condition',
-);
-
-my $areyousure = <<EOF;
-function areyousure_delete_log_email(logemailnum) {
-  if (confirm('Are you sure you want to delete log email condition #'+logemailnum+'?')) {
-${areyousure_onclick}
-  }
-}
-EOF
-
 my $editlink = [ $p.'edit/log_email.html?logemailnum=', 'logemailnum' ];
 
 </%init>
index 19b415d..b79aba9 100644 (file)
@@ -8,6 +8,12 @@
                               'labels' => { '' => '(all)', map { $_ => $_ } @contexts },
                               'curr_value' => scalar($cgi->param('context')),
                             },
+                            { 'field' => 'context_height',
+                              'type' => 'checkbox',
+                              'postfix' => 'Only match most specific context',
+                              'value' => 1,
+                              'curr_value' => scalar($cgi->param('context_height')),
+                            },
                             { 'field' => 'min_level',
                               'type'  => 'select',
                               'options' => [ &FS::Log::levelnums ],
@@ -24,6 +30,7 @@
                           ],
               'labels' => { 
                             'context' => 'Context',
+                            'context_height' => '',
                             'min_level' => 'Min. Level',
                             'to_addr' => 'To',
                             'msgnum' => 'Message',
@@ -44,6 +51,10 @@ die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right([ 'View system logs', 'Configuration' ]);
 
 my $msgnum = $cgi->param('msgnum');
+
+# XXX This attempt to set a default message isn't working, not sure why
+#     $msgnum gets set correctly, but isn't selected in the popup window...fix later
+
 unless ($msgnum) {
   my ($msg_template) = qsearch('msg_template',{ msgname => 'System log' });
   # doesn't seem worth having a config just for the default selected template
index 0478a80..17e9966 100644 (file)
@@ -305,6 +305,11 @@ my %substitutions = (
     '$payinfo'        => 'Card/account# (masked)',
     '$payinfo_end'    => 'Card/account last 4 digits',
   ],
+  'system_log' => [
+    '$loglevel'   => 'Log event severity level',
+    '$logcontext' => 'Log event context',
+    '$logmessage' => 'Log event message text'
+  ],
 );
 
 tie my %sections, 'Tie::IxHash', (
@@ -319,6 +324,7 @@ tie my %sections, 'Tie::IxHash', (
 'svc_domain'=> 'Domain service fields',
 'svc_phone' => 'Phone service fields',
 'svc_broadband' => 'Broadband service fields',
+'system_log' => 'System log fields',
 );
 
 my $widget = new HTML::Widgets::SelectLayers(
index cc17b15..5a6bdc0 100644 (file)
@@ -3,7 +3,7 @@
 % } else {
 <H1>Log email condition deleted</H1>
 <SCRIPT>
-window.top.location.reload();
+window.top.location = "<% $fsurl %>browse/log_email.html";
 </SCRIPT>
 % }
 
@@ -11,10 +11,12 @@ window.top.location.reload();
 die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right([ 'View system logs', 'Configuration' ]);
 
-my $logemailnum = $cgi->param('logemailnum');
-$logemailnum =~ /^\d+$/ or die "bad logemailnum '$logemailnum'";
-my $log_email = FS::log_email->by_key($logemailnum)
-  or die "logemailnum '$logemailnum' not found";
-my $error = $log_email->delete;
+  my $error;
+  my $logemailnum = $cgi->param('logemailnum');
+  if ( $logemailnum && $logemailnum =~ /^\d+$/ ) {
+    if ( my $log_email = FS::log_email->by_key( $logemailnum ) ) {
+      $error = $log_email->delete;
+    }
+  }
 </%init>
 
index 691a11e..9ddc97d 100644 (file)
@@ -39,8 +39,8 @@ Parameters:
             # Standard discount, not a waived setup fee
             my $discount = qsearchs('discount',{
                 discountnum => $_[0]->discountnum
-            });
-            return $discount->description;
+            }) || return 'Bad discountnum '.$_[0]->pkgdiscountnum;
+            return encode_entities $discount->description;
         } else {
             return 'Waive setup fee';
         }
@@ -53,7 +53,7 @@ Parameters:
             my $discount = qsearchs('discount',{
                 discountnum => $_[0]->discountnum
             });
-            return $discount->classname;
+            return encode_entities $discount->classname;
         } else {
             return 'n/a';
         }
index e1ce26d..9edd3ee 100755 (executable)
           <DIV STYLE="display:inline-block; vertical-align:baseline">
             <INPUT TYPE="radio" NAME="all_tags" VALUE="0" CHECKED> Any of these
             <BR>
-            <INPUT TYPE="radio" NAME="all_tags" VALUE="1"> All of these
+            <INPUT TYPE="radio" NAME="all_tags" VALUE="all"> All of these
+            <BR>
+            <INPUT TYPE="radio" NAME="all_tags" VALUE="none"> None of these
           </DIV>
         </TD>
       </TR>
index 66abffc..ac56fa7 100644 (file)
@@ -41,8 +41,8 @@
 
 % foreach my $contact ( $prospect_main->contact ) {
     <TR>
-      <TD ALIGN="right"><% $contact->contact_classname %> Contact</TD>
-      <TD BGCOLOR="#FFFFFF"><% $contact->line %></TD>
+      <TD ALIGN="right"><% $contact->contact_classname |h %> Contact</TD>
+      <TD BGCOLOR="#FFFFFF"><% $contact->line |h %></TD>
     </TR>
 %}