From: Ivan Kohler Date: Fri, 25 Aug 2017 22:06:19 +0000 (-0700) Subject: Merge branch 'master' of git.freeside.biz:/home/git/freeside X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=cbfeb5f6b7490f78361318ce6290bfb442dbfcbe;hp=5b5eb87bf66f1fac003a13dc2db48e8970c5c986 Merge branch 'master' of git.freeside.biz:/home/git/freeside --- diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 161e466a2..ccabf27fd 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -329,7 +329,7 @@ tie my %rights, 'Tie::IxHash', 'Usage: Unrateable CDRs', 'Usage: Time worked', #gone in 4.x as a distinct ACL (for now?) { rightname=>'Employees: Commission Report', global=>1 }, - { rightname=>'Employees: Audit Report', global=>1 }, + { rightname=>'Employee Reports', global=>1 }, #{ rightname => 'List customers of all agents', global=>1 }, ], diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index bddeee932..715733667 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -5796,8 +5796,8 @@ and customer address. Include units.', { 'key' => 'logout-timeout', - 'section' => 'UI', - 'description' => 'If set, automatically log users out of the backoffice after this many minutes.', + 'section' => 'deprecated', + 'description' => 'Deprecated. Used to automatically log users out of the backoffice after this many minutes. Set session timeouts in employee groups instead.', 'type' => 'text', }, diff --git a/FS/FS/Cron/backup.pm b/FS/FS/Cron/backup.pm index 7d868c882..5276565c5 100644 --- a/FS/FS/Cron/backup.pm +++ b/FS/FS/Cron/backup.pm @@ -25,7 +25,7 @@ sub backup { my $ext; if ( driver_name eq 'Pg' ) { - system("pg_dump -Fc $database >/var/tmp/$database.Pg"); + system("pg_dump -Fc -T h_cdr -T h_queue -T h_queue_arg $database >/var/tmp/$database.Pg"); $ext = 'Pg'; } elsif ( driver_name eq 'mysql' ) { system("mysqldump $database >/var/tmp/$database.sql"); diff --git a/FS/FS/Cron/rt_tasks.pm b/FS/FS/Cron/rt_tasks.pm index 01ea0b5dd..077f23cc6 100644 --- a/FS/FS/Cron/rt_tasks.pm +++ b/FS/FS/Cron/rt_tasks.pm @@ -31,6 +31,8 @@ sub rt_daily { my $system = $FS::TicketSystem::system; return if !defined($system) || $system ne 'RT_Internal'; + system('/opt/rt3/sbin/rt-fulltext-indexer --quiet --limit 5400 &'); + # if -d or -y is in use, bail out. There's no reliable way to tell RT # to use an alternate system time. if ( $opt{'d'} or $opt{'y'} ) { diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index f2e9e6fba..479f9b1f1 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2647,7 +2647,7 @@ sub ut_currency { =item ut_text COLUMN Check/untaint text. Alphanumerics, spaces, and the following punctuation -symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < > +symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < > ~ May not be null. If there is an error, returns the error, otherwise returns false. @@ -2661,7 +2661,7 @@ sub ut_text { # \p{Word} = alphanumerics, marks (diacritics), and connectors # see perldoc perluniprops $self->getfield($field) - =~ /^([\p{Word} \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>$money_char]+)$/ + =~ /^([\p{Word} \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>\~$money_char]+)$/ or return gettext('illegal_or_empty_text'). " $field: ". $self->getfield($field); $self->setfield($field,$1); diff --git a/FS/FS/TaxEngine/suretax.pm b/FS/FS/TaxEngine/suretax.pm index 1a00cdaaa..fe8764bf1 100644 --- a/FS/FS/TaxEngine/suretax.pm +++ b/FS/FS/TaxEngine/suretax.pm @@ -14,7 +14,7 @@ our $DEBUG = 1; # prints progress messages # $DEBUG = 2; # prints decoded request and response (noisy, be careful) # $DEBUG = 3; # prints raw response from the API, ridiculously unreadable -our $json = Cpanel::JSON::XS->new->pretty(1); +our $json = Cpanel::JSON::XS->new->pretty(0)->shrink(1); our %taxproduct_cache; @@ -328,13 +328,14 @@ sub make_taxlines { return; } - warn "sending SureTax request\n" if $DEBUG; + warn "encoding SureTax request\n" if $DEBUG; my $request_json = $json->encode($request); warn $request_json if $DEBUG > 1; my $host = $conf->config('suretax-hostname'); $host ||= 'testapi.taxrating.net'; + warn "sending SureTax request\n" if $DEBUG; # We are targeting the "V05" interface: # - accepts both telecom and general sales transactions # - produces results broken down by "invoice" (Freeside line item) @@ -346,8 +347,8 @@ sub make_taxlines { 'Accept' => 'application/json', ); + warn "received SureTax response\n" if $DEBUG; my $raw_response = $http_response->content; - warn "received response\n" if $DEBUG; warn $raw_response if $DEBUG > 2; my $response; if ( $raw_response =~ /^<\?xml/ ) { @@ -356,6 +357,8 @@ sub make_taxlines { $response = XMLin( $raw_response ); $raw_response = $response->{content}; } + + warn "decoding SureTax response\n" if $DEBUG; $response = eval { $json->decode($raw_response) } or die "$raw_response\n"; @@ -375,6 +378,7 @@ sub make_taxlines { } return if !$response->{GroupList}; + warn "creating FS objects from SureTax data\n" if $DEBUG; foreach my $taxable ( @{ $response->{GroupList} } ) { # each member of this array here corresponds to what SureTax calls an # "invoice" and we call a "line item". The invoice number is @@ -420,6 +424,7 @@ sub make_taxlines { }); } } + warn "TaxEngine/suretax.pm make_taxlines done; returning FS objects\n" if $DEBUG; return @elements; } diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm index 409b44136..4a360333e 100644 --- a/FS/FS/access_right.pm +++ b/FS/FS/access_right.pm @@ -155,6 +155,7 @@ sub _upgrade_data { # class method 'Refund payment' => [ 'Refund credit card payment', 'Refund Echeck payment' ], 'Regular void' => [ 'Void payments' ], 'Unvoid' => [ 'Unvoid payments', 'Unvoid invoices' ], + 'Employees: Audit Report' => [ 'Employee Reports' ], ); foreach my $oldright (keys %migrate) { @@ -233,9 +234,7 @@ sub _upgrade_data { # class method 'Usage: Unrateable CDRs', ], 'Provision customer service' => [ 'Edit password' ], - 'Financial reports' => [ 'Employees: Commission Report', - 'Employees: Audit Report', - ], + 'Financial reports' => 'Employee Reports', 'Change customer package' => 'Detach customer package', 'Services: Accounts' => 'Services: Cable Subscribers', 'Bulk change customer packages' => 'Bulk move customer services', diff --git a/FS/FS/access_user_session_log.pm b/FS/FS/access_user_session_log.pm new file mode 100644 index 000000000..d28ec8586 --- /dev/null +++ b/FS/FS/access_user_session_log.pm @@ -0,0 +1,124 @@ +package FS::access_user_session_log; +use base qw( FS::Record ); + +use strict; +#use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::access_user_session_log - Object methods for access_user_session_log records + +=head1 SYNOPSIS + + use FS::access_user_session_log; + + $record = new FS::access_user_session_log \%hash; + $record = new FS::access_user_session_log { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_user_session_log object represents an log of an employee session. +FS::access_user_session_log inherits from FS::Record. The following fields +are currently supported: + +=over 4 + +=item sessionlognum + +primary key + +=item usernum + +usernum + +=item start_date + +start_date + +=item last_date + +last_date + +=item logout_date + +logout_date + +=item logout_type + +logout_type + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new log entry. To add the entry 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 method. + +=cut + +sub table { 'access_user_session_log'; } + +=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 log entry. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_number('usernum') + || $self->ut_numbern('start_date') + || $self->ut_numbern('last_date') + || $self->ut_numbern('logout_date') + || $self->ut_text('logout_type') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L + +=cut + +1; + diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index d62120b3f..f16752ba4 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -6,6 +6,7 @@ use vars qw( $realtime_bop_decline_quiet ); #ugh use Carp; use Data::Dumper; use Business::CreditCard 0.35; +use Business::OnlinePayment; use FS::UID qw( dbh myconnect ); use FS::Record qw( qsearch qsearchs ); use FS::payby; diff --git a/FS/FS/cust_main/Import.pm b/FS/FS/cust_main/Import.pm index 646476162..9624529fa 100644 --- a/FS/FS/cust_main/Import.pm +++ b/FS/FS/cust_main/Import.pm @@ -325,6 +325,7 @@ sub batch_import { my %svc_x = (); my %bill_location = (); my %ship_location = (); + my $cust_payby = ''; foreach my $field ( @fields ) { if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) { @@ -409,17 +410,24 @@ sub batch_import { if ( $cust_main{'payinfo'} =~ /^\s*(\d+\@[\d\.]+)\s*$/ ) { - $cust_main{'payby'} = 'CHEK'; - $cust_main{'payinfo'} = $1; + delete $cust_main{'payinfo'}; - } else { + $cust_payby = new FS::cust_payby { + 'payby' => 'CHEK', + 'payinfo' => $1, + }; - $cust_main{'payby'} = 'CARD'; + } elsif ($cust_main{'payinfo'} =~ /^\s*([AD]?)(.*)\s*$/) { - if ($cust_main{'payinfo'} =~ /^\s*([AD]?)(.*)\s*$/) { - $cust_main{'payby'} = 'DCRD' if $1 eq 'D'; - $cust_main{'payinfo'} = $2; - } + delete $cust_main{'payinfo'}; + + $cust_payby = new FS::cust_payby { + 'payby' => ($1 eq 'D') ? 'DCRD' : 'CARD', + 'payinfo' => $2, + 'paycvv' => delete $cust_main{'paycvv'}, + 'paydate' => delete $cust_main{'paydate'}, + 'payname' => $cust_main{'first'}. ' '. $cust_main{'last'}, + }; } @@ -502,7 +510,10 @@ sub batch_import { $hash{$cust_pkg} = \@svc_x; } - my $error = $cust_main->insert( \%hash, $invoicing_list ); + my %options = ('invoicing_list' => $invoicing_list); + $options{'cust_payby'} = [ $cust_payby ] if $cust_payby; + + my $error = $cust_main->insert( \%hash, %options ); if ( $error ) { $dbh->rollback if $oldAutoCommit; diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index 1ef5387c1..8b6569a74 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -262,6 +262,17 @@ sub cust_statuscolor { : '000000'; } +=item agent_name + +=cut + +sub agent_name { + my $self = shift; + $self->cust_linked + ? $self->cust_main->agent_name + : $self->cust_unlinked_msg; +} + =item prospect_sql =item active_sql diff --git a/FS/FS/part_export/broadband_shellcommands.pm b/FS/FS/part_export/broadband_shellcommands.pm index 44280a200..d3e495c45 100644 --- a/FS/FS/part_export/broadband_shellcommands.pm +++ b/FS/FS/part_export/broadband_shellcommands.pm @@ -70,7 +70,18 @@ sub _export_command { my $command = $self->option($action); return '' if $command =~ /^\s*$/; - #set variables for the command + my $command_string = $self->_export_subvars( $svc_broadband, $command ); + + $self->shellcommands_queue( $svc_broadband->svcnum, + user => $self->option('user')||'root', + host => $self->machine, + command => $command_string, + ); +} + +sub _export_subvars { + my( $self, $svc_broadband, $command ) = @_; + no strict 'vars'; { no strict 'refs'; @@ -85,20 +96,25 @@ sub _export_command { $locationnum = $cust_pkg ? $cust_pkg->locationnum : ''; $custnum = $cust_pkg ? $cust_pkg->custnum : ''; - #done setting variables for the command + eval(qq("$command")); +} - $self->shellcommands_queue( $svc_broadband->svcnum, +sub _export_replace { + my($self, $new, $old ) = (shift, shift, shift); + my $command = $self->option('replace'); + + my $command_string = $self->_export_subvars_replace( $new, $old, $command ); + + $self->shellcommands_queue( $new->svcnum, user => $self->option('user')||'root', host => $self->machine, - command => eval(qq("$command")), + command => $command_string, ); } -sub _export_replace { - my($self, $new, $old ) = (shift, shift, shift); - my $command = $self->option('replace'); +sub _export_subvars_replace { + my( $self, $new, $old, $command ) = @_; - #set variable for the command no strict 'vars'; { no strict 'refs'; @@ -120,15 +136,10 @@ sub _export_replace { $new_locationnum = $new_cust_pkg ? $new_cust_pkg->locationnum : ''; $new_custnum = $new_cust_pkg ? $new_cust_pkg->custnum : ''; - #done setting variables for the command - - $self->shellcommands_queue( $new->svcnum, - user => $self->option('user')||'root', - host => $self->machine, - command => eval(qq("$command")), - ); + eval(qq("$command")); } + #a good idea to queue anything that could fail or take any time sub shellcommands_queue { my( $self, $svcnum ) = (shift, shift); diff --git a/FS/FS/part_export/broadband_shellcommands_expect.pm b/FS/FS/part_export/broadband_shellcommands_expect.pm new file mode 100644 index 000000000..ec525d38a --- /dev/null +++ b/FS/FS/part_export/broadband_shellcommands_expect.pm @@ -0,0 +1,19 @@ +package FS::part_export::broadband_shellcommands_expect; +use base qw( FS::part_export::shellcommands_expect ); + +use strict; +use FS::part_export::broadband_shellcommands; + +our %info = %FS::part_export::shellcommands_expect::info; +$info{'svc'} = 'svc_broadband'; +$info{'desc'} = 'Real time export via remote SSH, with interactive ("Expect"-like) scripting, for svc_broadband services'; + +sub _export_subvars { + FS::part_export::broadband_shellcommands::_export_subvars(@_) +} + +sub _export_subvars_replace { + FS::part_export::broadband_shellcommands::_export_subvars_replace(@_) +} + +1; diff --git a/FS/FS/part_export/pbxware.pm b/FS/FS/part_export/pbxware.pm index 4373e7ad5..9458fca0c 100644 --- a/FS/FS/part_export/pbxware.pm +++ b/FS/FS/part_export/pbxware.pm @@ -137,7 +137,7 @@ sub import_cdrs { # page's IDs or something. my $uniqueid = md5_hex(join(',',@$row)); if ( FS::cdr->row_exists('uniqueid = ?', $uniqueid) ) { - warn "skipped duplicate row in page $page\n" if $DEBUG > 1; + warn "skipped duplicate row in page $page\n" if $DEBUG; next CDR; } @@ -186,7 +186,7 @@ local $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0; ] ); warn "$me $method\n" if $DEBUG; - warn $request->as_string."\n" if $DEBUG > 1; + warn $request->as_string."\n" if $DEBUG; my $ua = LWP::UserAgent->new; my $response = $ua->request($request); diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index 647dc5f4d..775af17ae 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -4,6 +4,7 @@ use vars qw(@ISA %info); use Tie::IxHash; use Date::Format; use String::ShellQuote; +use Net::OpenSSH; use FS::part_export; use FS::Record qw( qsearch qsearchs ); @@ -296,7 +297,7 @@ sub _export_command_or_super { } else { $self->_export_command($action, @_); } -}; +} sub _export_command { my ( $self, $action, $svc_acct) = (shift, shift, shift); @@ -305,6 +306,41 @@ sub _export_command { return '' if $command =~ /^\s*$/; my $stdin = $self->option($action."_stdin"); + my( $command_string, $stdin_string ) = + $self->_export_subvars( $svc_acct, $command, $stdin ); + + $self->ssh_or_queue( $svc_acct, $command_string, $stdin_string ); +} + +sub ssh_or_queue { + my( $self, $svc_acct, $command_string, $stdin_string ) = @_; + + my @ssh_cmd_args = ( + user => $self->option('user') || 'root', + host => $self->svc_machine($svc_acct), + command => $command_string, + stdin_string => $stdin_string, + ignored_errors => $self->option('ignored_errors') || '', + ignore_all_errors => $self->option('ignore_all_errors'), + fail_on_output => $self->option('fail_on_output'), + ); + + if ( $self->option($action. '_no_queue') ) { + # discard return value just like freeside-queued. + eval { ssh_cmd(@ssh_cmd_args) }; + $error = $@; + $error = $error->full_message if ref $error; # Exception::Class::Base + return $error. + ' ('. $self->exporttype. ' to '. $self->svc_machine($svc_acct). ')' + if $error; + } else { + $self->shellcommands_queue( $svc_acct->svcnum, @ssh_cmd_args ); + } +} + +sub _export_subvars { + my( $self, $svc_acct, $command, $stdin ) = @_; + no strict 'vars'; { no strict 'refs'; @@ -412,27 +448,7 @@ sub _export_command { my $command_string = eval(qq("$command")); return "error filling in command: $@" if $@; - my @ssh_cmd_args = ( - user => $self->option('user') || 'root', - host => $self->svc_machine($svc_acct), - command => $command_string, - stdin_string => $stdin_string, - ignored_errors => $self->option('ignored_errors') || '', - ignore_all_errors => $self->option('ignore_all_errors'), - fail_on_output => $self->option('fail_on_output'), - ); - - if ( $self->option($action. '_no_queue') ) { - # discard return value just like freeside-queued. - eval { ssh_cmd(@ssh_cmd_args) }; - $error = $@; - $error = $error->full_message if ref $error; # Exception::Class::Base - return $error. - ' ('. $self->exporttype. ' to '. $self->svc_machine($svc_acct). ')' - if $error; - } else { - $self->shellcommands_queue( $svc_acct->svcnum, @ssh_cmd_args ); - } + ( $command_string, $stdin_string ); } sub _export_replace { @@ -440,6 +456,16 @@ sub _export_replace { my $command = $self->option('usermod'); return '' if $command =~ /^\s*$/; my $stdin = $self->option('usermod_stdin'); + + my( $command_string, $stdin_string ) = + $self->_export_subvars_replace( $new, $old, $command, $stdin ); + + $self->ssh_or_queue( $new, $command_string, $stdin_string ); +} + +sub _export_subvars_replace { + my( $self, $new, $old, $command, $stdin ) = @_; + no strict 'vars'; { no strict 'refs'; @@ -511,27 +537,7 @@ sub _export_replace { my $command_string = eval(qq("$command")); - my @ssh_cmd_args = ( - user => $self->option('user') || 'root', - host => $self->svc_machine($new), - command => $command_string, - stdin_string => $stdin_string, - ignored_errors => $self->option('ignored_errors') || '', - ignore_all_errors => $self->option('ignore_all_errors'), - fail_on_output => $self->option('fail_on_output'), - ); - - if($self->option('usermod_no_queue')) { - # discard return value just like freeside-queued. - eval { ssh_cmd(@ssh_cmd_args) }; - $error = $@; - $error = $error->full_message if ref $error; # Exception::Class::Base - return $error. ' ('. $self->exporttype. ' to '. $self->svc_machine($new). ')' - if $error; - } - else { - $self->shellcommands_queue( $new->svcnum, @ssh_cmd_args ); - } + ( $command_string, $stdin_string ); } #a good idea to queue anything that could fail or take any time @@ -545,7 +551,6 @@ sub shellcommands_queue { } sub ssh_cmd { #subroutine, not method - use Net::OpenSSH; my $opt = { @_ }; open my $def_in, '<', '/dev/null' or die "unable to open /dev/null\n"; my $ssh = Net::OpenSSH->new( diff --git a/FS/FS/part_export/shellcommands_expect.pm b/FS/FS/part_export/shellcommands_expect.pm new file mode 100644 index 000000000..c2a4118e2 --- /dev/null +++ b/FS/FS/part_export/shellcommands_expect.pm @@ -0,0 +1,128 @@ +package FS::part_export::shellcommands_expect; +use base qw( FS::part_export::shellcommands ); + +use strict; +use Tie::IxHash; +use Net::OpenSSH; +use Expect; +#use FS::Record qw( qsearch qsearchs ); + +tie my %options, 'Tie::IxHash', + 'user' => { label =>'Remote username', default=>'root' }, + 'useradd' => { label => 'Insert commands', type => 'textarea', }, + 'userdel' => { label => 'Delete commands', type => 'textarea', }, + 'usermod' => { label => 'Modify commands', type => 'textarea', }, + 'suspend' => { label => 'Suspend commands', type => 'textarea', }, + 'unsuspend' => { label => 'Unsuspend commands', type => 'textarea', }, + 'debug' => { label => 'Enable debugging', + type => 'checkbox', + value => 1, + }, +; + +our %info = ( + 'svc' => 'svc_acct', + 'desc' => 'Real time export via remote SSH, with interactive ("Expect"-like) scripting, for svc_acct services', + 'options' => \%options, + 'notes' => q[ +Interactively run commands via SSH in a remote terminal, like "Expect". In +most cases, you probably want a regular shellcommands (or broadband_shellcommands, etc.) export instead, unless +you have a specific need to interact with a terminal-based interface in an +"Expect"-like fashion. +

+ +Each line specifies a string to match and a command to +run after that string is found, separated by the first space. For example, to +run "exit" after a prompt ending in "#" is sent, "# exit". You will need to +setup SSH for unattended operation. +

+ +In commands, all variable substitutions of the regular shellcommands (or +broadband_shellcommands, etc.) export are available (use a backslash to escape +a literal $). +] +); + +sub _export_command { + my ( $self, $action, $svc_acct) = (shift, shift, shift); + my @lines = split("\n", $self->option($action) ); + + return '' unless @lines; + + my @commands = (); + foreach my $line (@lines) { + my($match, $command) = split(' ', $line, 2); + my( $command_string ) = $self->_export_subvars( $svc_acct, $command, '' ); + push @commands, [ $match, $command_string ]; + } + + $self->shellcommands_expect_queue( $svc_acct->svcnum, @commands ); +} + +sub _export_replace { + my( $self, $new, $old ) = (shift, shift, shift); + my @lines = split("\n", $self->option('replace') ); + + return '' unless @lines; + + my @commands = (); + foreach my $line (@lines) { + my($match, $command) = split(' ', $line, 2); + my( $command_string ) = $self->_export_subvars_replace( $new, $old, $command, '' ); + push @commands, [ $match, $command_string ]; + } + + $self->shellcommands_expect_queue( $new->svcnum, @commands ); +} + +sub shellcommands_expect_queue { + my( $self, $svcnum, @commands ) = @_; + + my $queue = new FS::queue { + 'svcnum' => $svcnum, + 'job' => "FS::part_export::shellcommands_expect::ssh_expect", + }; + $queue->insert( + user => $self->option('user') || 'root', + host => $self->machine, + debug => $self->option('debug'), + commands => \@commands, + ); +} + +sub ssh_expect { #subroutine, not method + my $opt = { @_ }; + + my $dest = $opt->{'user'}.'@'.$opt->{'host'}; + + open my $def_in, '<', '/dev/null' or die "unable to open /dev/null\n"; + my $ssh = Net::OpenSSH->new( $dest, 'default_stdin_fh' => $def_in ); + # ignore_all_errors doesn't override SSH connection/auth errors-- + # probably correct + die "Couldn't establish SSH connection to $dest: ". $ssh->error + if $ssh->error; + + my ($pty, $pid) = $ssh->open2pty + or die "Couldn't start a remote terminal session"; + my $expect = Expect->init($pty); + #not useful #$expect->debug($opt->{debug} ? 3 : 0); + + foreach my $line ( @{ $opt->{commands} } ) { + my( $match, $command ) = @$line; + + warn "Waiting for '$match'\n" if $opt->{debug}; + + my $matched = $expect->expect(30, $match); + unless ( $matched ) { + my $err = "Never saw '$match'\n"; + warn $err; + die $err; + } + warn "Running '$command'\n" if $opt->{debug}; + $expect->send("$command\n"); + } + + ''; +} + +1; diff --git a/FS/FS/part_export/vitelity.pm b/FS/FS/part_export/vitelity.pm index 332e45712..51bb0aab1 100644 --- a/FS/FS/part_export/vitelity.pm +++ b/FS/FS/part_export/vitelity.pm @@ -425,7 +425,7 @@ sub e911_send { my $e911_result = $self->vitelity_command('e911send', %e911send); - unless ( $e911_result =~ /^(missingdata|invalid)/i ) { + unless ( $e911_result =~ /status=(missingdata|invalid)/i ) { warn "Vitelity response: $e911_result" if $self->option('debug'); return ''; } diff --git a/FS/bin/freeside-voipinnovations-cdrimport b/FS/bin/freeside-voipinnovations-cdrimport index 23ea6bbdc..d64c8708f 100755 --- a/FS/bin/freeside-voipinnovations-cdrimport +++ b/FS/bin/freeside-voipinnovations-cdrimport @@ -4,7 +4,8 @@ use strict; use Getopt::Std; use Date::Format; use File::Temp 'tempdir'; -use Net::FTP; +use Net::SSLGlue::FTP; #at least until the Deb 9 transition is done, then + # regular Net::FTP has SSL support use FS::UID qw(adminsuidsetup datasrc dbh); use FS::cdr; use FS::cdr_batch; @@ -39,11 +40,14 @@ my $tempdir = tempdir( CLEANUP => !$opt_v ); my $format = 'voip_innovations'; my $hostname = 'customercdr.voipinnovations.com'; -my $ftp = Net::FTP->new($hostname, Debug => $opt_d) +my $ftp = Net::FTP->new($hostname, Passive => 1, Debug => $opt_d) or die "Can't connect to $hostname: $@\n"; +$ftp->starttls() + or die "TLS initialization failed: ". $ftp->message. "\n"; + $ftp->login($login, $password) - or die "Login failed: ".$ftp->message."\n"; + or die "Login failed: ". $ftp->message. "\n"; ### # get the file list @@ -51,7 +55,7 @@ $ftp->login($login, $password) warn "Retrieving directory listing\n" if $opt_v; -$ftp->cwd('/'); +#$ftp->cwd('/'); my @dirs = $ftp->ls(); warn scalar(@dirs)." directories found.\n" if $opt_v; # apply date range diff --git a/FS/t/access_user_session_log.t b/FS/t/access_user_session_log.t new file mode 100644 index 000000000..630637474 --- /dev/null +++ b/FS/t/access_user_session_log.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_user_session_log; +$loaded=1; +print "ok 1\n"; diff --git a/debian/control b/debian/control index a268ffdad..ab5802123 100644 --- a/debian/control +++ b/debian/control @@ -16,8 +16,9 @@ Package: freeside Architecture: all Pre-Depends: freeside-lib # dbconfig-common -Depends: ${perl:Depends}, ${shlibs:Depends}, ${misc:Depends}, freeside-webui, - debconf, cron, openbsd-inetd, tcpd, undersmtpd, ssmtp, freeside-lib (>= 4.0~git-20160211) +Depends: ${perl:Depends}, ${shlibs:Depends}, ${misc:Depends}, + freeside-webui (= ${binary:Version}), freeside-lib (= ${binary:Version}), + debconf, cron, openbsd-inetd, tcpd, undersmtpd, ssmtp Description: Billing and trouble ticketing for service providers Freeside is a web-based billing, trouble ticketing and network monitoring application. It includes features for ISPs and WISPs, hosting providers and @@ -99,7 +100,7 @@ Depends: aspell-en,gnupg,ghostscript,gsfonts,gzip,latex-xcolor, libxml-writer-perl, libio-socket-ssl-perl, libmap-splat-perl, libdatetime-format-ical-perl, librest-client-perl, libgeo-streetaddress-us-perl, libbusiness-onlinepayment-perl, - libnet-vitelity-perl (>= 0.05) + libnet-vitelity-perl (>= 0.05), libnet-sslglue-perl, libexpect-perl Conflicts: libparams-classify-perl (>= 0.013-6) Replaces: freeside (<<4) Breaks: freeside (<<4) diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index e58441d24..05bf4377a 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -296,8 +296,13 @@ if ( $cgi->param('error') ) { $custnum=''; $cust_main = new FS::cust_main ( {} ); + + my @agentnums = $curuser->agentnums; + $cust_main->agentnum( $agentnums[0] ) + if scalar(@agentnums) == 1; $cust_main->agentnum( $conf->config('default_agentnum') ) if $conf->exists('default_agentnum'); + $cust_main->referral_custnum( $cgi->param('referral_custnum') ); $cust_main->set('postal_invoice', 'Y') unless $conf->exists('disablepostalinvoicedefault'); diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 58a7d5783..cadbd864d 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -428,8 +428,6 @@ $report_logs{'Billing events'} = [ $fsurl.'search/report_cust_event.html', 'Sea if $curuser->access_right('Billing event reports'); $report_logs{'Credit limit incidents'} = [ $fsurl.'search/report_cust_main_credit_limit.html', '' ] if $curuser->access_right('List rating data'); -$report_logs{'Employee activity'} = [ $fsurl.'search/report_employee_audit.html', '' ] - if $curuser->access_right('Employees: Audit Report'); $report_logs{'System log'} = [ $fsurl.'search/log.html', 'View system events and debugging information.' ], if $curuser->access_right('View system logs') || $curuser->access_right('Configuration'); @@ -437,6 +435,12 @@ $report_logs{'Outgoing messages'} = [ $fsurl.'search/cust_msg.html', 'View outgo if $curuser->access_right('View email logs') || $curuser->access_right('Configuration'); +tie my %report_employee, 'Tie::IxHash', + 'Employee activity' => [ $fsurl.'search/report_employee_audit.html', '' ], + 'Employee sessions' => [ $fsurl.'search/report_access_user_session_log.html', '' ], + 'Access log statistics' => [ $fsurl.'search/report_access_user_log.html?group_by=path', '' ], +; + tie my %report_menu, 'Tie::IxHash'; $report_menu{'Saved searches'} = [ \%report_saved_searches, 'My saved searches' ] if keys(%report_saved_searches); @@ -475,6 +479,8 @@ $report_menu{'Financial (Receivables)'} = [ \%report_financial, 'Financial repor $report_menu{'Financial (Payables)'} = [ \%report_payable, 'Financial reports (Payables)' ] if $curuser->access_right('Financial reports'); +$report_menu{'Employees'} = [ \%report_employee, 'Employee reports' ] + if $curuser->access_right('Employee Reports'); $report_menu{'Logs'} = [ \%report_logs, 'System and email logs' ] if (keys %report_logs); # empty if the user has no rights to it $report_menu{'SQL Query'} = [ $fsurl.'search/report_sql.html', 'SQL Query'] @@ -554,8 +560,6 @@ $tools_system{'Status'} = [ $fsurl.'view/Status.html', 'System status' ] if $curuser->access_right('Configuration'); # 'View system status'); $tools_system{'Job Queue'} = [ $fsurl.'search/queue.html', 'View pending job queue' ] if $curuser->access_right('Job queue'); -$tools_system{'Access log statistics'} = [ $fsurl.'search/report_access_user_log.html?group_by=path', '' ] - if $curuser->access_right('Configuration'); # 'View profiling data'); tie my %tools_menu, 'Tie::IxHash', (); $tools_menu{'Customers'} = [ \%tools_customers, 'Customer tools' ] diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html index 3c1862212..96cf6412c 100755 --- a/httemplate/misc/cancel_pkg.html +++ b/httemplate/misc/cancel_pkg.html @@ -197,25 +197,27 @@ my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum}) my $part_pkg = $cust_pkg->part_pkg; -my @unprovision_warning; -{ - my @services_w_export; - for ( $cust_pkg->cust_svc ) { - push( @services_w_export, ($_->label)[0] . ': ' . ($_->label)[1], ) - if $_->part_svc->export_svc; - } - if ( @services_w_export ) { - push( @unprovision_warning, 'NOTE: This package has ' - . @services_w_export . ' ' . PL( "service", @services_w_export ) - . ' that will be unprovisioned', ); - if ( @services_w_export < 10 ) { - $unprovision_warning[0] .= ':'; - push( @unprovision_warning, @services_w_export, ); - } - else { - $unprovision_warning[0] .= '.'; - } +my @unprovision_warning = (); +unless ( $method =~ /^(resume|uncancel)$/ ) { + my @services_w_export = map { my @l = $_->label; $l[0]. ': '. $l[1]; } + grep $_->part_svc->export_svc, + $cust_pkg->cust_svc; + if ( @services_w_export ) { + + my $actioned = ($method =~ /^(suspend|adjourn)$/) ? 'suspended' + : 'unprovisioned'; + push @unprovision_warning, + 'NOTE: This package has '. @services_w_export. ' '. + PL( 'service', @services_w_export ). " that will be $actioned"; + + if ( @services_w_export < 10 ) { + $unprovision_warning[0] .= ':'; + push @unprovision_warning, @services_w_export; + } else { + $unprovision_warning[0] .= '.'; } + + } } $date ||= $cust_pkg->get($method); diff --git a/httemplate/search/employee_audit.html b/httemplate/search/employee_audit.html index 2bc6ff46e..991758c4a 100644 --- a/httemplate/search/employee_audit.html +++ b/httemplate/search/employee_audit.html @@ -7,7 +7,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report'); + unless $FS::CurrentUser::CurrentUser->access_right('Employee Reports'); my %tables = ( cust_pay => 'Payments', diff --git a/httemplate/search/report_access_user_log.html b/httemplate/search/report_access_user_log.html index 0c8acb35e..d43c74274 100644 --- a/httemplate/search/report_access_user_log.html +++ b/httemplate/search/report_access_user_log.html @@ -27,7 +27,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + unless $FS::CurrentUser::CurrentUser->access_right('Employee Reports'); my $group_by = ''; if ( $cgi->param('group_by') =~ /^(\w+)$/ ) { diff --git a/httemplate/search/report_employee_audit.html b/httemplate/search/report_employee_audit.html index 461849b76..6008e1caa 100644 --- a/httemplate/search/report_employee_audit.html +++ b/httemplate/search/report_employee_audit.html @@ -23,7 +23,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report'); + unless $FS::CurrentUser::CurrentUser->access_right('Employee Reports'); my %tables = ( cust_pay => 'Payments', diff --git a/httemplate/view/Status-db_size_detail.html b/httemplate/view/Status-db_size_detail.html new file mode 100644 index 000000000..96c2da006 --- /dev/null +++ b/httemplate/view/Status-db_size_detail.html @@ -0,0 +1,39 @@ +<& /elements/header-popup.html, { + 'title' => 'Database size details', + } +&> + +<& /search/elements/search.html, + 'name_singular' => 'table', + 'header' => [ 'Table', 'Size' ], + 'query' => $query, + 'count_query' => $count_query, + 'nohtmlheader' => 1, +&> + +<& /elements/footer-popup.html &> +<%init> + +my $query = q{ + + SELECT table_name, pg_size_pretty(total_bytes) AS total + FROM ( + SELECT * FROM ( + SELECT relname AS TABLE_NAME, + pg_total_relation_size(c.oid) AS total_bytes + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE relkind = 'r' + AND nspname = 'public' + ) a + ) a order by total_bytes desc +}; + +my $count_query = q{ + SELECT count(*) FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE relkind = 'r' + AND nspname = 'public' +}; + + diff --git a/httemplate/view/Status.html b/httemplate/view/Status.html index e08bfe44b..7fb03eb2f 100644 --- a/httemplate/view/Status.html +++ b/httemplate/view/Status.html @@ -1,4 +1,7 @@ <& /elements/header.html, 'System Status' &> + +<& /elements/init_overlib.html &> + % foreach my $section ( keys %status ) { <% mt($section) |h %> @@ -11,6 +14,7 @@


% } + <& /elements/footer.html &> <%init> @@ -40,8 +44,13 @@ if ( $db eq 'PostgreSQL' && $db_ver =~ /^\s*PostgreSQL\s+([\w\.]+)\s+on\s+/ ) { my $db_size = 'Unknown'; if ( $db eq 'PostgreSQL' ) { $db_size = FS::Record->scalar_sql(qq( - SELECT pg_size_pretty(pg_database_size('freeside')) - )); + SELECT pg_size_pretty(pg_database_size('freeside')) + )). ' '. + include('/elements/popup_link.html', + 'action' => 'Status-db_size_detail.html', + 'label' => '(details)', + 'actionlabel' => 'Database size details', + ); } tie my %status, 'Tie::IxHash', diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html index 6241f11b8..6402383d8 100644 --- a/httemplate/view/cust_main/payment_history/payment.html +++ b/httemplate/view/cust_main/payment_history/payment.html @@ -180,17 +180,18 @@ if ( $cust_pay->closed !~ /^Y/i } my $void = ''; -# note: "TOKN" is not yet supported in stock freeside -my $voidmsg = $cust_pay->payby =~ /^(CARD|CHEK|TOKN)$/ +my $voidmsg = $cust_pay->payby =~ /^(CARD|CHEK)$/ ? ' (' . emt('do not send anything to the payment gateway').')' : ''; $void = ' ('. - include( '/elements/popup_link.html', - 'label' => emt('void'), - 'action' => "${p}misc/void-cust_pay.html?".$cust_pay->paynum, - 'actionlabel' => emt('Void payment'), - ). - ')' + include( '/elements/popup_link.html', + 'label' => emt('void'), + 'action' => "${p}misc/void-cust_pay.html?".$cust_pay->paynum, + 'actionlabel' => emt('Void payment'), + 'title' => emt('Void this payment from the database'). + $voidmsg, + ). + ')' if $cust_pay->closed !~ /^Y/i && ( ( $cust_pay->payby eq 'CARD' && $opt{'Credit card void'} ) || ( $cust_pay->payby eq 'CHEK' && $opt{'Echeck void'} )