diff options
51 files changed, 961 insertions, 189 deletions
diff --git a/FS/FS/ClientAPI/Freeside.pm b/FS/FS/ClientAPI/Freeside.pm index 8aa61e6..42b9c42 100644 --- a/FS/FS/ClientAPI/Freeside.pm +++ b/FS/FS/ClientAPI/Freeside.pm @@ -44,16 +44,34 @@ sub freesideinc_service { return { 'error' => 'bad support-key' }; } + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + my $custnum = $cust_pkg->custnum; + + my $quantity = $packet->{'quantity'} || 1; + + #false laziness w/webservice_log.pm + my $color = 1.10; + my $page = 0.10; + #XXX check if some customers can use some API calls, rate-limiting, etc. # but for now, everybody can use everything + if ( $packet->{method} eq 'print' ) { + my $avail_credit = $cust_pkg->cust_main->credit_limit + - $color - $quantity * $page + - FS::webservice_log->price_print( + 'custnum' => $custnum, + ); + + return { 'error' => 'Over credit limit' } + if $avail_credit <= 0; + } #record it happened - my $custnum = $svc_acct->cust_svc->cust_pkg->custnum; my $webservice_log = new FS::webservice_log { 'custnum' => $custnum, 'svcnum' => $svc_acct->svcnum, 'method' => $packet->{'method'}, - 'quantity' => $packet->{'quantity'} || 1, + 'quantity' => $quantity, }; my $error = $webservice_log->insert; return { 'error' => $error } if $error; diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index 3167aa0..e69a06e 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -181,6 +181,7 @@ sub ss2clientapi { 'reset_passwd' => 'MyAccount/reset_passwd', 'check_reset_passwd' => 'MyAccount/check_reset_passwd', 'process_reset_passwd' => 'MyAccount/process_reset_passwd', + 'validate_passwd' => 'MyAccount/validate_passwd', 'list_tickets' => 'MyAccount/list_tickets', 'create_ticket' => 'MyAccount/create_ticket', 'get_ticket' => 'MyAccount/get_ticket', diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index ec317ba..0d561a2 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -669,6 +669,7 @@ my %batch_gateway_options = ( ); map { $_->gatewaynum, $_->label } @gateways; }, + 'per_agent' => 1, ); my %invoice_mode_options = ( @@ -1565,6 +1566,14 @@ and customer address. Include units.', }, { + 'key' => 'invoice_omit_due_date', + 'section' => 'invoice_balances', + 'description' => 'Omit the "Please pay by (date)" from invoices.', + 'type' => 'checkbox', + 'per_agent' => 1, + }, + + { 'key' => 'invoice_sections', 'section' => 'invoicing', 'description' => 'Split invoice into sections and label according to package category when enabled.', @@ -2909,6 +2918,13 @@ and customer address. Include units.', }, { + 'key' => 'selfservice-password_reset_hours', + 'section' => 'self-service', + 'description' => 'Numbers of hours an email password reset is valid. Defaults to 24.', + 'type' => 'text', + }, + + { 'key' => 'selfservice-password_reset_msgnum', 'section' => 'self-service', 'description' => 'Template to use for password reset emails.', diff --git a/FS/FS/ConfDefaults.pm b/FS/FS/ConfDefaults.pm index 2fa8344..2c24b13 100644 --- a/FS/FS/ConfDefaults.pm +++ b/FS/FS/ConfDefaults.pm @@ -33,6 +33,9 @@ sub cust_fields_avail { ( 'Cust# | Cust. Status | Customer' => 'custnum | Status | Last, First or Company (Last, First)', + 'Agent | Agent Cust# or Cust# | Cust. Status | Customer' => + 'Agent | Agent Cust# | Status | Last, First or Company (Last, First)', + 'Customer | Day phone | Night phone | Mobile phone | Fax number' => 'Customer | (all phones)', 'Cust# | Customer | Day phone | Night phone | Mobile phone | Fax number' => diff --git a/FS/FS/Cron/pay_batch.pm b/FS/FS/Cron/pay_batch.pm index 9791749..1e110f2 100644 --- a/FS/FS/Cron/pay_batch.pm +++ b/FS/FS/Cron/pay_batch.pm @@ -22,6 +22,38 @@ $me = '[FS::Cron::pay_batch]'; # -r: Multi-process mode dry run option # -a: Only process customers with the specified agentnum +sub batch_gateways { + my $conf = FS::Conf->new; + # returns a list of arrayrefs: [ gateway, payby, agentnum ] + my %opt = @_; + my @agentnums; + if ( $conf->exists('batch-spoolagent') ) { + if ( $opt{a} ) { + @agentnums = split(',', $opt{a}); + } else { + @agentnums = map { $_->agentnum } qsearch('agent'); + } + } else { + @agentnums = (''); + if ( $opt{a} ) { + warn "Payment batch processing skipped in per-agent mode.\n" if $DEBUG; + return; + } + } + my @return; + foreach my $agentnum (@agentnums) { + my %gateways; + foreach my $payby ('CARD', 'CHEK') { + my $gatewaynum = $conf->config("batch-gateway-$payby", $agentnum); + next if !$gatewaynum; + my $gateway = FS::payment_gateway->by_key($gatewaynum) + or die "payment_gateway '$gatewaynum' not found\n"; + push @return, [ $gateway, $payby, $agentnum ]; + } + } + @return; +} + sub pay_batch_submit { my %opt = @_; local $DEBUG = ($opt{l} || 1) if $opt{v}; @@ -31,25 +63,14 @@ sub pay_batch_submit { my $dbh = dbh; warn "$me batch_submit\n" if $DEBUG; - my $conf = FS::Conf->new; - - # need to respect -a somehow, but for now none of this is per-agent - if ( $opt{a} ) { - warn "Payment batch processing skipped in per-agent mode.\n" if $DEBUG; - return; - } - my %gateways; - foreach my $payby ('CARD', 'CHEK') { - my $gatewaynum = $conf->config("batch-gateway-$payby"); - next if !$gatewaynum; - my $gateway = FS::payment_gateway->by_key($gatewaynum) - or die "payment_gateway '$gatewaynum' not found\n"; - + foreach my $config (batch_gateways(%opt)) { + my ($gateway, $payby, $agentnum) = @$config; if ( $gateway->batch_processor->can('default_transport') ) { - foreach my $pay_batch ( - qsearch('pay_batch', { status => 'O', payby => $payby }) - ) { + my $search = { status => 'O', payby => $payby }; + $search->{agentnum} = $agentnum if $agentnum; + + foreach my $pay_batch ( qsearch('pay_batch', $search) ) { warn "Exporting batch ".$pay_batch->batchnum."\n" if $DEBUG; eval { $pay_batch->export_to_gateway( $gateway, debug => $DEBUG ); }; @@ -80,38 +101,28 @@ sub pay_batch_receive { my $error; warn "$me batch_receive\n" if $DEBUG; - my $conf = FS::Conf->new; - # need to respect -a somehow, but for now none of this is per-agent - if ( $opt{a} ) { - warn "Payment batch processing skipped in per-agent mode.\n" if $DEBUG; - return; - } - my %gateways; - foreach my $payby ('CARD', 'CHEK') { - my $gatewaynum = $conf->config("batch-gateway-$payby"); - next if !$gatewaynum; - # If the same gateway is selected for both paybys, only import it once - $gateways{$gatewaynum} = FS::payment_gateway->by_key($gatewaynum); - if ( !$gateways{$gatewaynum} ) { + my %gateway_done; + # If a gateway is selected for more than one payby+agentnum, still + # only import from it once; we expect it will send back multiple + # result batches. + foreach my $config (batch_gateways(%opt)) { + my ($gateway, $payby, $agentnum) = @$config; + next if $gateway_done{$gateway->gatewaynum}; + next unless $gateway->batch_processor->can('default_transport'); + # already warned about this above + warn "Importing results from '".$gateway->label."'\n" if $DEBUG; + # Note that import_from_gateway is not agent-limited; if a gateway + # returns results for batches not associated with this agent, we will + # still accept them. Well-behaved gateways will not do that. + $error = eval { + FS::pay_batch->import_from_gateway( gateway =>$gateway, debug => $DEBUG ) + } || $@; + if ( $error ) { + # this we can roll back $dbh->rollback; - die "batch-gateway-$payby gateway $gatewaynum not found\n"; + die "error receiving from gateway '".$gateway->label."':\n$error\n"; } - } - - foreach my $gateway (values %gateways) { - if ( $gateway->batch_processor->can('default_transport') ) { - warn "Importing results from '".$gateway->label."'\n" if $DEBUG; - $error = eval { - FS::pay_batch->import_from_gateway( gateway =>$gateway, debug => $DEBUG ) - } || $@; - if ( $error ) { - # this we can roll back - $dbh->rollback; - die "error receiving from gateway '".$gateway->label."':\n$error\n"; - } - } - # else we already warned about it above } #$gateway # resolve batches if we can diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm index 9a43180..a2d1b3e 100644 --- a/FS/FS/Misc.pm +++ b/FS/FS/Misc.pm @@ -256,10 +256,17 @@ sub send_email { } push @to, $options{bcc} if defined($options{bcc}); + # fully unpack all addresses found in @to (including Bcc) to make the + # envelope list + my @env_to; + foreach my $dest (@to) { + push @env_to, map { $_->address } Email::Address->parse($dest); + } + local $@; # just in case eval { sendmail($message, { transport => $transport, from => $from, - to => \@to }) }; + to => \@env_to }) }; my $error = ''; if(ref($@) and $@->isa('Email::Sender::Failure')) { @@ -274,7 +281,7 @@ sub send_email { if ( $conf->exists('log_sent_mail') ) { my $cust_msg = FS::cust_msg->new({ 'env_from' => $options{'from'}, - 'env_to' => join(', ', @to), + 'env_to' => join(', ', @env_to), 'header' => $message->header_as_string, 'body' => $message->body_as_string, '_date' => $time, diff --git a/FS/FS/Setup.pm b/FS/FS/Setup.pm index 0c3226a..f005a36 100644 --- a/FS/FS/Setup.pm +++ b/FS/FS/Setup.pm @@ -7,7 +7,6 @@ use vars qw( @EXPORT_OK ); use Tie::IxHash; use Crypt::OpenSSL::RSA; use FS::UID qw( dbh driver_name ); -#use FS::Record; use FS::svc_domain; $FS::svc_domain::whois_hack = 1; @@ -99,6 +98,12 @@ sub enable_encryption { $conf->set('encryptionpublickey', $rsa->get_public_key_string ); $conf->set('encryptionprivatekey', $rsa->get_private_key_string ); + # reload Record globals, false laziness with FS::Record + $FS::Record::conf_encryption = $conf->exists('encryption'); + $FS::Record::conf_encryptionmodule = $conf->config('encryptionmodule'); + $FS::Record::conf_encryptionpublickey = join("\n",$conf->config('encryptionpublickey')); + $FS::Record::conf_encryptionprivatekey = join("\n",$conf->config('encryptionprivatekey')); + } sub enable_banned_pay_pad { diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index caa31f7..d7add71 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -1952,7 +1952,8 @@ sub balance_due_msg { # (yes, or if invoice_sections is enabled; this is just for compatibility) if ( $self->due_date ) { $msg .= ' - ' . $self->mt('Please pay by'). ' '. - $self->due_date2str('short'); + $self->due_date2str('short') + unless $self->conf->config_bool('invoice_omit_due_date'); } elsif ( $self->terms ) { $msg .= ' - '. $self->mt($self->terms); } diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index 04aeda1..6cc04b9 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -300,7 +300,7 @@ sub cust_header { my %header2method = ( 'Customer' => 'name', 'Cust. Status' => 'cust_status_label', - 'Cust#' => 'custnum', + 'Cust#' => 'display_custnum', 'Name' => 'contact', 'Company' => 'company', @@ -347,6 +347,8 @@ sub cust_header { # 'Payment Type' => 'cust_payby', 'Current Balance' => 'current_balance', 'Agent Cust#' => 'agent_custid', + 'Agent' => 'agent_name', + 'Agent Cust# or Cust#' => 'display_custnum', 'Advertising Source' => 'referral', ); $header2method{'Cust#'} = 'display_custnum' @@ -450,6 +452,8 @@ sub cust_sql_fields { } push @fields, 'agent_custid'; + push @fields, 'agentnum' if grep { $_ eq 'agent_name' } @cust_fields; + my @extra_fields = (); if (grep { $_ eq 'current_balance' } @cust_fields) { push @extra_fields, FS::cust_main->balance_sql . " AS current_balance"; diff --git a/FS/FS/UI/Web/small_custview.pm b/FS/FS/UI/Web/small_custview.pm index e82e332..85bee7d 100644 --- a/FS/FS/UI/Web/small_custview.pm +++ b/FS/FS/UI/Web/small_custview.pm @@ -142,16 +142,25 @@ sub small_custview { } $html .= '</TD></TR><TR><TD></TD><TD BGCOLOR="#ffffff">'; - if ( $cust_main->daytime && $cust_main->night ) { - $html .= ( FS::Msgcat::_gettext('daytime') || 'Day' ). - ' '. $cust_main->daytime. - '<BR>'. ( FS::Msgcat::_gettext('night') || 'Night' ). - ' '. $cust_main->night; - } elsif ( $cust_main->daytime || $cust_main->night ) { - $html .= $cust_main->daytime || $cust_main->night; + + my $num_numbers = 0; + $num_numbers++ foreach grep $cust_main->$_(), qw( daytime night mobile ); + if ( $num_numbers > 1 ) { + $html .= ucfirst( FS::Msgcat::_gettext('daytime') ). + ' '. $cust_main->daytime. '<BR>' + if $cust_main->daytime; + $html .= ucfirst( FS::Msgcat::_gettext('night') ). + ' '. $cust_main->night. '<BR>' + if $cust_main->night; + $html .= ucfirst( FS::Msgcat::_gettext('mobile') ). + ' '. $cust_main->mobile. '<BR>' + if $cust_main->night; + } elsif ( $num_numbers ) { # == 1 ) { + $html .= ( $cust_main->daytime || $cust_main->night || $cust_main->mobile ). + '<BR>'; } if ( $cust_main->fax ) { - $html .= '<BR>Fax '. $cust_main->fax; + $html .= 'Fax '. $cust_main->fax; } $html .= '</TD></TR></TABLE></TD>'; diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm index a8aa43b..e49f6df 100644 --- a/FS/FS/contact.pm +++ b/FS/FS/contact.pm @@ -873,7 +873,10 @@ sub send_reset_email { 'svcnum' => $opt{'svcnum'}, }; - my $timeout = '24 hours'; #? + + my $conf = new FS::Conf; + my $timeout = + ($conf->config('selfservice-password_reset_hours') || 24 ). ' hours'; my $reset_session_id; do { @@ -885,8 +888,6 @@ sub send_reset_email { #email it - my $conf = new FS::Conf; - my $cust_main = ''; my @cust_contact = grep $_->selfservice_access, $self->cust_contact; $cust_main = $cust_contact[0]->cust_main if scalar(@cust_contact) == 1; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 71552b0..4bd3f26 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -5784,13 +5784,94 @@ sub _upgrade_data { #class method $class->_upgrade_otaker(%opts); + # turn on encryption as part of regular upgrade, so all new records are immediately encrypted + # existing records will be encrypted in queueable_upgrade (below) + unless ($conf->exists('encryptionpublickey') || $conf->exists('encryptionprivatekey')) { + eval "use FS::Setup"; + die $@ if $@; + FS::Setup::enable_encryption(); + } + } sub queueable_upgrade { my $class = shift; + + ### encryption gets turned on in _upgrade_data, above + + eval "use FS::upgrade_journal"; + die $@ if $@; + + # prior to 2013 (commit f16665c9) payinfo was stored in history if not encrypted, + # clear that out before encrypting/tokenizing anything else + if (!FS::upgrade_journal->is_done('clear_payinfo_history')) { + foreach my $table ('cust_payby','cust_pay_pending','cust_pay','cust_pay_void','cust_refund') { + my $sql = 'UPDATE h_'.$table.' SET payinfo = NULL WHERE payinfo IS NOT NULL'; + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute or die $sth->errstr; + } + FS::upgrade_journal->set_done('clear_payinfo_history'); + } + + # encrypt old records + if ($conf->exists('encryption') && !FS::upgrade_journal->is_done('encryption_check')) { + + # allow replacement of closed cust_pay/cust_refund records + local $FS::payinfo_Mixin::allow_closed_replace = 1; + + # because it looks like nothing's changing + local $FS::Record::no_update_diff = 1; + + # commit everything immediately + local $FS::UID::AutoCommit = 1; + + # encrypt what's there + foreach my $table ('cust_payby','cust_pay_pending','cust_pay','cust_pay_void','cust_refund') { + my $tclass = 'FS::'.$table; + my $lastrecnum = 0; + my @recnums = (); + while (my $recnum = _upgrade_next_recnum(dbh,$table,\$lastrecnum,\@recnums)) { + my $record = $tclass->by_key($recnum); + next unless $record; # small chance it's been deleted, that's ok + next unless grep { $record->payby eq $_ } @FS::Record::encrypt_payby; + # window for possible conflict is practically nonexistant, + # but just in case... + $record = $record->select_for_update; + my $error = $record->replace; + die $error if $error; + } + } + + FS::upgrade_journal->set_done('encryption_check'); + } + + # now that everything's encrypted, tokenize... FS::cust_main::Billing_Realtime::token_check(@_); } +# not entirely false laziness w/ Billing_Realtime::_token_check_next_recnum +# cust_payby might get deleted while this runs +# not a method! +sub _upgrade_next_recnum { + my ($dbh,$table,$lastrecnum,$recnums) = @_; + my $recnum = shift @$recnums; + return $recnum if $recnum; + my $tclass = 'FS::'.$table; + my $sql = 'SELECT '.$tclass->primary_key. + ' FROM '.$table. + ' WHERE '.$tclass->primary_key.' > '.$$lastrecnum. + ' ORDER BY '.$tclass->primary_key.' LIMIT 500';; + my $sth = $dbh->prepare($sql) or die $dbh->errstr; + $sth->execute() or die $sth->errstr; + my @recnums; + while (my $rec = $sth->fetchrow_hashref) { + push @$recnums, $rec->{$tclass->primary_key}; + } + $sth->finish(); + $$lastrecnum = $$recnums[-1]; + return shift @$recnums; +} + =back =head1 BUGS diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 4821ce5..6932647 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -544,14 +544,19 @@ sub bill { foreach my $part_pkg ( @part_pkg ) { - $cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill ); + my $this_cust_pkg = $cust_pkg; + # for add-on packages, copy the object to avoid leaking changes back to + # the caller if pkg_list is in use; see RT#73607 + if ( $part_pkg->get('pkgpart') != $real_pkgpart ) { + $this_cust_pkg = FS::cust_pkg->new({ %hash }); + } my $pass = ''; - if ( $cust_pkg->separate_bill ) { + if ( $this_cust_pkg->separate_bill ) { # if no_auto is also set, that's fine. we just need to not have # invoices that are both auto and no_auto, and since the package # gets an invoice all to itself, it will only be one or the other. - $pass = $cust_pkg->pkgnum; + $pass = $this_cust_pkg->pkgnum; if (!exists $cust_bill_pkg{$pass}) { # it may not exist yet push @passes, $pass; $total_setup{$pass} = do { my $z = 0; \$z }; @@ -565,17 +570,17 @@ sub bill { ); $cust_bill_pkg{$pass} = []; } - } elsif ( ($cust_pkg->no_auto || $part_pkg->no_auto) ) { + } elsif ( ($this_cust_pkg->no_auto || $part_pkg->no_auto) ) { $pass = 'no_auto'; } - my $next_bill = $cust_pkg->getfield('bill') || 0; + my $next_bill = $this_cust_pkg->getfield('bill') || 0; my $error; # let this run once if this is the last bill upon cancellation while ( $next_bill <= $cmp_time or $options{cancel} ) { $error = $self->_make_lines( 'part_pkg' => $part_pkg, - 'cust_pkg' => $cust_pkg, + 'cust_pkg' => $this_cust_pkg, 'precommit_hooks' => \@precommit_hooks, 'line_items' => $cust_bill_pkg{$pass}, 'setup' => $total_setup{$pass}, @@ -590,12 +595,12 @@ sub bill { last if $error; # or if we're not incrementing the bill date. - last if ($cust_pkg->getfield('bill') || 0) == $next_bill; + last if ($this_cust_pkg->getfield('bill') || 0) == $next_bill; # or if we're letting it run only once last if $options{cancel}; - $next_bill = $cust_pkg->getfield('bill') || 0; + $next_bill = $this_cust_pkg->getfield('bill') || 0; #stop if -o was passed to freeside-daily last if $options{'one_recur'}; diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index b15920b..64ce7ec 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -1216,6 +1216,30 @@ sub _upgrade_data { #class method ### $class->upgrade_set_cardtype; + # for batch payments, make sure paymask is set + do { + local $FS::payinfo_Mixin::allow_closed_replace = 1; + local $FS::payinfo_Mixin::ignore_masked_payinfo = 1; + + my $cursor = FS::Cursor->new({ + table => 'cust_pay', + extra_sql => ' WHERE paymask IS NULL AND payinfo IS NOT NULL + AND payby IN(\'CARD\', \'CHEK\') + AND batchnum IS NOT NULL', + }); + + # records from cursors for some reason don't decrypt payinfo, so + # call replace_old to fetch the record "normally" + while (my $cust_pay = $cursor->fetch) { + $cust_pay = $cust_pay->replace_old; + $cust_pay->set('paymask', $cust_pay->mask_payinfo); + my $error = $cust_pay->replace; + if ($error) { + die "$error (setting masked payinfo on payment#". $cust_pay->paynum. + ")\n" + } + } + }; } sub process_upgrade_paybatch { diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm index 8127c6a..d29c6d0 100644 --- a/FS/FS/cust_pay_batch.pm +++ b/FS/FS/cust_pay_batch.pm @@ -297,6 +297,7 @@ sub approve { 'custnum' => $new->custnum, 'payby' => $new->payby, 'payinfo' => $new->payinfo || $old->payinfo, + 'paymask' => $new->mask_payinfo, 'paid' => $new->paid, '_date' => $new->_date, 'usernum' => $new->usernum, diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index bcb5176..b491f91 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -38,6 +38,8 @@ use FS::sales; # for modify_charge use FS::cust_credit; +use Data::Dumper; + # temporary fix; remove this once (un)suspend admin notices are cleaned up use FS::Misc qw(send_email); @@ -3017,7 +3019,7 @@ sub modify_charge { $pkg_opt_modified = 1; } } - $pkg_opt_modified = 1 if (scalar(@old_additional) - 1) != $i; + $pkg_opt_modified = 1 if scalar(@old_additional) != $i; $pkg_opt{'additional_count'} = $i if $i > 0; my $old_classnum; @@ -3171,9 +3173,6 @@ sub modify_charge { ''; } - - -use Data::Dumper; sub process_bulk_cust_pkg { my $job = shift; my $param = shift; @@ -5582,6 +5581,32 @@ sub _upgrade_data { # class method my $error = $part_pkg_link->remove_linked; die $error if $error; } + + # RT#73607: canceling a package with billing addons sometimes changes its + # pkgpart. + # Find records where the last replace_new record for the package before it + # was canceled has a different pkgpart from the package itself. + my @cust_pkg = qsearch({ + 'table' => 'cust_pkg', + 'select' => 'cust_pkg.*, h_cust_pkg.pkgpart AS h_pkgpart', + 'addl_from' => ' JOIN ( + SELECT pkgnum, MAX(historynum) AS historynum FROM h_cust_pkg + WHERE cancel IS NULL + AND history_action = \'replace_new\' + GROUP BY pkgnum + ) AS last_history USING (pkgnum) + JOIN h_cust_pkg USING (historynum)', + 'extra_sql' => ' WHERE cust_pkg.cancel is not null + AND cust_pkg.pkgpart != h_cust_pkg.pkgpart' + }); + foreach my $cust_pkg ( @cust_pkg ) { + my $pkgnum = $cust_pkg->pkgnum; + warn "fixing pkgpart on canceled pkg#$pkgnum\n"; + $cust_pkg->set('pkgpart', $cust_pkg->h_pkgpart); + my $error = $cust_pkg->replace; + die $error if $error; + } + } =back diff --git a/FS/FS/cust_pkg/Search.pm b/FS/FS/cust_pkg/Search.pm index 89809de..311dbdb 100644 --- a/FS/FS/cust_pkg/Search.pm +++ b/FS/FS/cust_pkg/Search.pm @@ -281,7 +281,7 @@ sub search { } ### - # parse refnum (advertising source) + # parse (customer) refnum (advertising source) ### if ( exists($params->{'refnum'}) ) { @@ -292,7 +292,7 @@ sub search { @refnum = ( $params->{'refnum'} ); } my $in = join(',', grep /^\d+$/, @refnum); - push @where, "refnum IN($in)" if length $in; + push @where, "cust_main.refnum IN($in)" if length $in; } ### @@ -450,7 +450,8 @@ sub search { '' => {}, ); - if( exists($params->{'active'} ) ) { + if ( exists($params->{'active'} ) ) { + # This overrides all the other date-related fields, and includes packages # that were active at some time during the interval. It excludes: # - packages that were set up after the end of the interval @@ -464,40 +465,51 @@ sub search { "(cust_pkg.cancel IS NULL OR cust_pkg.cancel >= $beginning )", "(cust_pkg.susp IS NULL OR cust_pkg.susp >= $beginning )", "NOT (".FS::cust_pkg->onetime_sql . ")"; - } - else { + + } else { + my $exclude_change_from = 0; my $exclude_change_to = 0; foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel )) { - next unless exists($params->{$field}); + if ( $params->{$field.'_null'} ) { + + push @where, "cust_pkg.$field IS NULL"; + # this should surely be obsoleted by now: OR cust_pkg.$field == 0 ) - my($beginning, $ending) = @{$params->{$field}}; + } else { - next if $beginning == 0 && $ending == 4294967295; + next unless exists($params->{$field}); + + my($beginning, $ending) = @{$params->{$field}}; + + next if $beginning == 0 && $ending == 4294967295; + + push @where, + "cust_pkg.$field IS NOT NULL", + "cust_pkg.$field >= $beginning", + "cust_pkg.$field <= $ending"; + + $orderby ||= "ORDER BY cust_pkg.$field"; + + if ( $field eq 'setup' ) { + $exclude_change_from = 1; + } elsif ( $field eq 'cancel' ) { + $exclude_change_to = 1; + } elsif ( $field eq 'change_date' ) { + # if we are given setup and change_date ranges, and the setup date + # falls in _both_ ranges, then include the package whether it was + # a change or not + $exclude_change_from = 0; + } - push @where, - "cust_pkg.$field IS NOT NULL", - "cust_pkg.$field >= $beginning", - "cust_pkg.$field <= $ending"; - - $orderby ||= "ORDER BY cust_pkg.$field"; - - if ( $field eq 'setup' ) { - $exclude_change_from = 1; - } elsif ( $field eq 'cancel' ) { - $exclude_change_to = 1; - } elsif ( $field eq 'change_date' ) { - # if we are given setup and change_date ranges, and the setup date - # falls in _both_ ranges, then include the package whether it was - # a change or not - $exclude_change_from = 0; } + } if ($exclude_change_from) { - push @where, "change_pkgnum IS NULL"; + push @where, "cust_pkg.change_pkgnum IS NULL"; } if ($exclude_change_to) { # a join might be more efficient here @@ -506,6 +518,7 @@ sub search { WHERE cust_pkg.pkgnum = changed_to_pkg.change_pkgnum )"; } + } $orderby ||= 'ORDER BY bill'; diff --git a/FS/FS/msg_template/email.pm b/FS/FS/msg_template/email.pm index 5abbaca..63c860f 100644 --- a/FS/FS/msg_template/email.pm +++ b/FS/FS/msg_template/email.pm @@ -290,7 +290,7 @@ sub prepare { my @to; if ( exists($opt{'to'}) ) { - @to = split(/\s*,\s*/, $opt{'to'}); + @to = map { $_->format } Email::Address->parse($opt{'to'}); } elsif ( $cust_main ) { @@ -393,14 +393,17 @@ sub prepare { # effective To: address (not in headers) push @to, $self->bcc_addr if $self->bcc_addr; - my $env_to = join(', ', @to); + my @env_to; + foreach my $dest (@to) { + push @env_to, map { $_->address } Email::Address->parse($dest); + } my $cust_msg = FS::cust_msg->new({ 'custnum' => $cust_main ? $cust_main->custnum : '', 'msgnum' => $self->msgnum, '_date' => $time, 'env_from' => $env_from, - 'env_to' => $env_to, + 'env_to' => join(',', @env_to), 'header' => $message->header_as_string, 'body' => $message->body_as_string, 'error' => '', @@ -507,7 +510,9 @@ sub send_prepared { $domain = $1; } - my @to = split(/\s*,\s*/, $cust_msg->env_to); + # in principle should already be a list of bare addresses, but run it + # through Email::Address to make sure + my @env_to = map { $_->address } Email::Address->parse($cust_msg->env_to); my %smtp_opt = ( 'host' => $conf->config('smtpmachine'), 'helo' => $domain ); @@ -533,7 +538,7 @@ sub send_prepared { eval { sendmail( $message, { transport => $transport, from => $cust_msg->env_from, - to => \@to }) + to => \@env_to }) }; my $error = ''; if(ref($@) and $@->isa('Email::Sender::Failure')) { diff --git a/FS/FS/part_export/broadband_sqlradius.pm b/FS/FS/part_export/broadband_sqlradius.pm index e58c641..2d6681e 100644 --- a/FS/FS/part_export/broadband_sqlradius.pm +++ b/FS/FS/part_export/broadband_sqlradius.pm @@ -133,6 +133,8 @@ sub radius_check_suspended { sub _export_suspend { my( $self, $svc_broadband ) = (shift, shift); + return '' if $self->option('skip_provisioning'); + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index f0ef3fc..9e65e51 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -26,6 +26,10 @@ tie %options, 'Tie::IxHash', type => 'select', options => [qw( usergroup radusergroup ) ], }, + 'skip_provisioning' => { + type => 'checkbox', + label => 'Skip provisioning records to this database' + }, 'ignore_accounting' => { type => 'checkbox', label => 'Ignore accounting records from this database' @@ -154,6 +158,8 @@ sub radius_check { #override for other svcdb sub _export_insert { my($self, $svc_x) = (shift, shift); + return '' if $self->option('skip_provisioning'); + foreach my $table (qw(reply check)) { my $method = "radius_$table"; my %attrib = $self->$method($svc_x); @@ -179,6 +185,8 @@ sub _export_insert { sub _export_replace { my( $self, $new, $old ) = (shift, shift, shift); + return '' if $self->option('skip_provisioning'); + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; @@ -289,6 +297,8 @@ sub _export_replace { sub _export_suspend { my( $self, $svc_acct ) = (shift, shift); + return '' if $self->option('skip_provisioning'); + my $new = $svc_acct->clone_suspended; local $SIG{HUP} = 'IGNORE'; @@ -360,6 +370,8 @@ sub _export_suspend { sub _export_unsuspend { my( $self, $svc_x ) = (shift, shift); + return '' if $self->option('skip_provisioning'); + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; @@ -399,6 +411,8 @@ sub _export_unsuspend { sub _export_delete { my( $self, $svc_x ) = (shift, shift); + return '' if $self->option('skip_provisioning'); + my $jobnum = ''; my $usergroup = $self->option('usergroup') || 'usergroup'; diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 35f178e..bb8c6bc 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -401,19 +401,20 @@ I<bulk_skip>, I<provision_hold> and I<options> If I<pkg_svc> is set to a hashref with svcparts as keys and quantities as values, the appropriate FS::pkg_svc records will be replaced. I<hidden_svc> -can be set to a hashref of svcparts and flag values ('Y' or '') to set the -'hidden' field in these records. I<bulk_skip> and I<provision_hold> can be set -to a hashref of svcparts and flag values ('Y' or '') to set the respective field -in those records. +can be set to a hashref of svcparts and flag values ('Y' or '') to set the +'hidden' field in these records. I<bulk_skip> and I<provision_hold> can be +set to a hashref of svcparts and flag values ('Y' or '') to set the +respective field in those records. -If I<primary_svc> is set to the svcpart of the primary service, the appropriate -FS::pkg_svc record will be updated. +If I<primary_svc> is set to the svcpart of the primary service, the +appropriate FS::pkg_svc record will be updated. -If I<options> is set to a hashref, the appropriate FS::part_pkg_option records -will be replaced. +If I<options> is set to a hashref, the appropriate FS::part_pkg_option +records will be replaced. If I<part_pkg_currency> is set to a hashref of options (with the keys as -option_CURRENCY), appropriate FS::part_pkg::currency records will be replaced. +option_CURRENCY), appropriate FS::part_pkg::currency records will be +replaced. =cut @@ -2345,6 +2346,56 @@ sub queueable_upgrade { die $error if $error; } } + + # remove custom flag from one-time charge packages that were accidentally + # flagged as custom + $search = FS::Cursor->new({ + 'table' => 'part_pkg', + 'hashref' => { 'freq' => '0', + 'custom' => 'Y', + 'family_pkgpart' => { op => '!=', value => '' }, + }, + 'addl_from' => ' JOIN + (select pkgpart from cust_pkg group by pkgpart having count(*) = 1) + AS singular_pkg USING (pkgpart)', + }); + my @fields = grep { $_ ne 'pkgpart' + and $_ ne 'custom' + and $_ ne 'disabled' } FS::part_pkg->fields; + PKGPART: while (my $part_pkg = $search->fetch) { + # can't merge the package back into its parent (too late for that) + # but we can remove the custom flag if it's not actually customized, + # i.e. nothing has been changed. + + my $family_pkgpart = $part_pkg->family_pkgpart; + next PKGPART if $family_pkgpart == $part_pkg->pkgpart; + my $parent_pkg = FS::part_pkg->by_key($family_pkgpart); + foreach my $field (@fields) { + if ($part_pkg->get($field) ne $parent_pkg->get($field)) { + next PKGPART; + } + } + # options have to be identical too + # but links, FCC options, discount plans, and usage packages can't be + # changed through the "modify charge" UI, so skip them + my %newopt = $part_pkg->options; + my %oldopt = $parent_pkg->options; + OPTION: foreach my $option (keys %newopt) { + if (delete $newopt{$option} ne delete $oldopt{$option}) { + next PKGPART; + } + } + if (keys(%newopt) or keys(%oldopt)) { + next PKGPART; + } + # okay, now replace it + warn "Removing custom flag from part_pkg#".$part_pkg->pkgpart."\n"; + $part_pkg->set('custom', ''); + my $error = $part_pkg->replace; + die $error if $error; + } # $search->fetch + + return; } =item curuser_pkgs_sql diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index df97286..d96c472 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -191,6 +191,9 @@ tie my %accountcode_tollfree_field, 'Tie::IxHash', 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where dcontext is set to any of these (comma-separated) values: ', }, + 'skip_dcontext_prefix' => { 'name' => 'Do not charge for CDRs where dcontext starts with: ', + }, + 'skip_dcontext_suffix' => { 'name' => 'Do not charge for CDRs where dcontext ends with: ', }, @@ -336,7 +339,8 @@ tie my %accountcode_tollfree_field, 'Tie::IxHash', use_cdrtypenum ignore_cdrtypenum use_calltypenum ignore_calltypenum ignore_disposition disposition_in disposition_prefix - skip_dcontext skip_dcontext_suffix skip_dst_prefix + skip_dcontext skip_dcontext_prefix skip_dcontext_suffix + skip_dst_prefix skip_dstchannel_prefix skip_src_length_more noskip_src_length_accountcode_tollfree accountcode_tollfree_ratenum accountcode_tollfree_field @@ -608,6 +612,12 @@ sub check_chargable { if $self->option_cacheable('skip_dcontext') =~ /\S/ && grep { $cdr->dcontext eq $_ } split(/\s*,\s*/, $self->option_cacheable('skip_dcontext')); + my $len_dcontext_prefix = + length($self->option_cacheable('skip_dcontext_prefix')); + return "dcontext starts with ". $self->option_cacheable('skip_dcontext_prefix') + if $len_dcontext_prefix + && substr($cdr->dcontext,0,$len_dcontext_prefix) eq $self->option_cacheable('skip_dcontext_prefix'); + my $len_suffix = length($self->option_cacheable('skip_dcontext_suffix')); return "dcontext ends with ". $self->option_cacheable('skip_dcontext_suffix') if $len_suffix diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm index 1049751..4aeb331 100644 --- a/FS/FS/pay_batch.pm +++ b/FS/FS/pay_batch.pm @@ -14,6 +14,7 @@ use FS::Record qw( dbh qsearch qsearchs ); use FS::Conf; use FS::cust_pay; use FS::Log; +use Try::Tiny; =head1 NAME @@ -1086,16 +1087,21 @@ sub export_to_gateway { return ''; } - my $batch = Business::BatchPayment->create(Batch => - batch_id => $self->batchnum, - items => \@items - ); - $processor->submit($batch); + try { + my $batch = Business::BatchPayment->create(Batch => + batch_id => $self->batchnum, + items => \@items + ); + $processor->submit($batch); - if ($batch->processor_id) { - $self->set('processor_id',$batch->processor_id); - $self->replace; - } + if ($batch->processor_id) { + $self->set('processor_id',$batch->processor_id); + $self->replace; + } + } catch { + $dbh->rollback if $oldAutoCommit; + die $_; + }; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; diff --git a/FS/FS/pay_batch/paymentech.pm b/FS/FS/pay_batch/paymentech.pm index 1282507..3cf3134 100644 --- a/FS/FS/pay_batch/paymentech.pm +++ b/FS/FS/pay_batch/paymentech.pm @@ -175,7 +175,14 @@ sub _upgrade_gateway { my $conf = FS::Conf->new; my @batchconfig = $conf->config('batchconfig-paymentech'); my %options; - @options{ qw(bin terminalID merchantID login password ) } = @batchconfig; + @options{ qw( + bin + terminalID + merchantID + login + password + with_recurringInd + ) } = @batchconfig; $options{'industryType'} = 'EC'; ( 'Paymentech', %options ); } diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm index 3a51022..1c45720 100644 --- a/FS/FS/payinfo_Mixin.pm +++ b/FS/FS/payinfo_Mixin.pm @@ -307,13 +307,12 @@ sub payinfo_used { my $payinfo = shift || $self->payinfo; my %hash = ( 'custnum' => $self->custnum, - 'payby' => 'CARD', + 'payby' => $self->payby, ); return 1 if qsearch('cust_pay', { %hash, 'payinfo' => $payinfo } ) - || qsearch('cust_pay', - { %hash, 'paymask' => $self->mask_payinfo('CARD', $payinfo) } ) + || qsearch('cust_pay', { %hash, 'paymask' => $self->mask_payinfo } ) ; return 0; diff --git a/FS/FS/webservice_log.pm b/FS/FS/webservice_log.pm index 7e320c2..1dfabe6 100644 --- a/FS/FS/webservice_log.pm +++ b/FS/FS/webservice_log.pm @@ -125,6 +125,40 @@ sub check { =back +=head1 CLASS METHODS + +=over 4 + +=item price_print + +Calculates cost of printing unbilled print jobs for this customer. + +=cut + +sub price_print { + my( $class, %opt ) = @_; + +# $opt{'beginning'} ||= 0; +# $opt{'ending'} ||= 4294967295; + + #false laziness w/ClientAPI/Freeside.pm + my $color = 1.10; + my $page = 0.10; + + $class->scalar_sql(" + SELECT SUM( $color + quantity * $page ) + FROM webservice_log + WHERE custnum = $opt{custnum} + AND method = 'print' + AND status IS NULL + "); +# AND _date >= $opt{beginning} +# AND _date < $opt{ending} + +} + +=back + =head1 BUGS =head1 SEE ALSO diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade index b8a8fbd..1684408 100755 --- a/FS/bin/freeside-upgrade +++ b/FS/bin/freeside-upgrade @@ -473,11 +473,9 @@ Also performs other upgrade functions: [ -r ]: Skip sqlradius updates. Useful for occassions where the sqlradius databases may be inaccessible. - [ -j ]: Run certain upgrades asychronously from the job queue. Currently - used only for the 2.x -> 3.x cust_location, cust_pay and part_pkg - upgrades. This may cause odd behavior before the upgrade is - complete, so it's recommended only for very large cust_main, cust_pay - and/or part_pkg tables that take too long to upgrade. + [ -j ]: Run certain upgrades asychronously from the job queue. Recommended + for very large cust_main or part_pkg tables that take too long to + upgrade. [ -a ]: Run schema changes in parallel (Pg only). DBIx::DBSchema minimum version 0.41 recommended. Recommended only for large databases and diff --git a/FS/t/suite/15-activate_encryption.t b/FS/t/suite/15-activate_encryption.t new file mode 100755 index 0000000..e5732f7 --- /dev/null +++ b/FS/t/suite/15-activate_encryption.t @@ -0,0 +1,106 @@ +#!/usr/bin/perl + +use strict; +use FS::Test; +use Test::More tests => 13; +use FS::Conf; +use FS::UID qw( dbh ); +use DateTime; +use FS::cust_main; # to load all other tables + +my $fs = FS::Test->new( user => 'admin' ); +my $conf = FS::Conf->new; +my $err; +my @tables = qw(cust_payby cust_pay_pending cust_pay cust_pay_void cust_refund); + +### can only run on test database (company name "Freeside Test") +like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT(''); + +### we need to unencrypt our test db before we can test turning it on + +# temporarily load all payinfo into memory +my %payinfo = (); +foreach my $table (@tables) { + $payinfo{$table} = {}; + foreach my $record ($fs->qsearch({ table => $table })) { + next unless grep { $record->payby eq $_ } @FS::Record::encrypt_payby; + $payinfo{$table}{$record->get($record->primary_key)} = $record->get('payinfo'); + } +} + +# turn off encryption +foreach my $config ( qw(encryption encryptionmodule encryptionpublickey encryptionprivatekey) ) { + $conf->delete($config); + ok( !$conf->exists($config), "deleted $config" ) or BAIL_OUT(''); +} +$FS::Record::conf_encryption = $conf->exists('encryption'); +$FS::Record::conf_encryptionmodule = $conf->config('encryptionmodule'); +$FS::Record::conf_encryptionpublickey = join("\n",$conf->config('encryptionpublickey')); +$FS::Record::conf_encryptionprivatekey = join("\n",$conf->config('encryptionprivatekey')); + +# save unencrypted values +foreach my $table (@tables) { + local $FS::payinfo_Mixin::allow_closed_replace = 1; + local $FS::Record::no_update_diff = 1; + local $FS::UID::AutoCommit = 1; + my $tclass = 'FS::'.$table; + foreach my $key (keys %{$payinfo{$table}}) { + my $record = $tclass->by_key($key); + $record->payinfo($payinfo{$table}{$key}); + $err = $record->replace; + last if $err; + } +} +ok( !$err, "save unencrypted values" ) or BAIL_OUT($err); + +# make sure it worked +CHECKDECRYPT: +foreach my $table (@tables) { + my $tclass = 'FS::'.$table; + foreach my $key (sort {$a <=> $b} keys %{$payinfo{$table}}) { + my $sql = 'SELECT * FROM '.$table. + ' WHERE payinfo LIKE \'M%\''. + ' AND char_length(payinfo) > 80'. + ' AND '.$tclass->primary_key.' = '.$key; + my $sth = dbh->prepare($sql) or BAIL_OUT(dbh->errstr); + $sth->execute or BAIL_OUT($sth->errstr); + if (my $hashrec = $sth->fetchrow_hashref) { + $err = $table.' '.$key.' encrypted'; + last CHECKDECRYPT; + } + } +} +ok( !$err, "all values unencrypted" ) or BAIL_OUT($err); + +### now, run upgrade +$err = system('freeside-upgrade','admin'); +ok( !$err, 'upgrade ran' ) or BAIL_OUT('Error string: '.$!); + +# check that confs got set +foreach my $config ( qw(encryption encryptionmodule encryptionpublickey encryptionprivatekey) ) { + ok( $conf->exists($config), "$config was set" ) or BAIL_OUT(''); +} + +# check that known records got encrypted +CHECKENCRYPT: +foreach my $table (@tables) { + my $tclass = 'FS::'.$table; + foreach my $key (sort {$a <=> $b} keys %{$payinfo{$table}}) { + my $sql = 'SELECT * FROM '.$table. + ' WHERE payinfo LIKE \'M%\''. + ' AND char_length(payinfo) > 80'. + ' AND '.$tclass->primary_key.' = '.$key; + my $sth = dbh->prepare($sql) or BAIL_OUT(dbh->errstr); + $sth->execute or BAIL_OUT($sth->errstr); + unless ($sth->fetchrow_hashref) { + $err = $table.' '.$key.' not encrypted'; + last CHECKENCRYPT; + } + } +} +ok( !$err, "all values encrypted" ) or BAIL_OUT($err); + +exit; + +1; + diff --git a/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html b/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html index f396682..40eed80 100644 --- a/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html +++ b/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html @@ -13,20 +13,25 @@ <TABLE WIDTH="100%"> <TR> <TD WIDTH="50%"> -<%= if ($previous < $beginning) { - $OUT .= qq!<A HREF="${url}view_cdr_details;svcnum=$svcnum;beginning=!; - $OUT .= qq!$previous;ending=$beginning">Previous period</A>!; - }else{ +<%= + $ahref = qq!<A HREF="${url}view_cdr_details;svcnum=$svcnum;!; + $ahref = qq!inbound=1;! if $inbound; + if ($previous < $beginning) { + $OUT .= $ahref. + qq!beginning=$previous;ending=$beginning">Previous period</A>!; + } else { ''; - } %> + } +%> </TD> <TD WIDTH="50%" ALIGN="right"> -<%= if ($next > $ending) { - $OUT .= qq!<A HREF="${url}view_cdr_details;svcnum=$svcnum;beginning=!; - $OUT .= qq!$ending;ending=$next">Next period</A>!; - }else{ +<%= + if ($next > $ending) { + $OUT .= $ahref. qq!beginning=$ending;ending=$next">Next period</A>!; + } else { ''; - }%> + } +%> </TD> </TR> </TABLE> diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html index 753bc76..6d15164 100644 --- a/httemplate/edit/agent_payment_gateway.html +++ b/httemplate/edit/agent_payment_gateway.html @@ -12,6 +12,8 @@ Use gateway <SELECT NAME="gatewaynum"> % foreach my $payment_gateway ( % qsearch('payment_gateway', { 'disabled' => '' } ) % ) { +% # don't let these be selected as agent overrides; there's a different mechanism +% next if $payment_gateway->gateway_namespace eq 'Business::BatchPayment'; % <OPTION VALUE="<% $payment_gateway->gatewaynum %>"><% $payment_gateway->gateway_module %> (<% $payment_gateway->gateway_username %>) diff --git a/httemplate/edit/cust_pkg_discount.html b/httemplate/edit/cust_pkg_discount.html index e1e3dae..79c3478 100755 --- a/httemplate/edit/cust_pkg_discount.html +++ b/httemplate/edit/cust_pkg_discount.html @@ -1,13 +1,17 @@ <& /elements/header-popup.html, "Discount Package" &> <& /elements/error.html &> -<FORM NAME="DiscountPkgForm" ACTION="<% $p %>edit/process/cust_pkg_discount.html" METHOD=POST> +<FORM NAME = "DiscountPkgForm" + ACTION = "<% $p %>edit/process/cust_pkg_discount.html" + METHOD = POST + onSubmit = "document.DiscountPkgForm.submit.disabled=true;" +> <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> <% ntable('#cccccc') %> <TR> - <TH ALIGN="right">Current package </TH> + <TH ALIGN="right">Package </TH> <TD COLSPAN=7> <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %> </TD> @@ -18,6 +22,8 @@ curr_value_recur => $recur_discountnum, disable_setup => $disable_setup, disable_recur => $disable_recur, + setup_label => emt('Setup fee discount'), + recur_label => emt('Recurring fee discount'), &> </TABLE> diff --git a/httemplate/elements/tr-select-pkg-discount.html b/httemplate/elements/tr-select-pkg-discount.html index dc38cff..0c57fd8 100644 --- a/httemplate/elements/tr-select-pkg-discount.html +++ b/httemplate/elements/tr-select-pkg-discount.html @@ -34,7 +34,7 @@ description if curr_value_setup is set. Likewise "disable_recur". % if ( $curuser->access_right('Waive setup fee') ) { % push @$pre_options, -2 => 'Waive setup fee'; % } -<& tr-td-label.html, label => emt('Setup fee') &> +<& tr-td-label.html, label => $opt{setup_label} || emt('Setup fee') &> <td> <& select-discount.html, field => 'setup_discountnum', @@ -97,7 +97,7 @@ description if curr_value_setup is set. Likewise "disable_recur". % if ( $curuser->access_right('Discount customer package') % and !$opt{disable_recur} ) { -<& tr-td-label.html, label => emt('Recurring fee') &> +<& tr-td-label.html, label => $opt{recur_label} || emt('Recurring fee') &> <td> <& select-discount.html, field => 'recur_discountnum', diff --git a/httemplate/misc/bulk_change_pkg.cgi b/httemplate/misc/bulk_change_pkg.cgi index 4964e59..6ed272f 100755 --- a/httemplate/misc/bulk_change_pkg.cgi +++ b/httemplate/misc/bulk_change_pkg.cgi @@ -10,16 +10,32 @@ %# some false laziness w/search/cust_pkg.cgi <INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords |h %>"> -% for my $param (qw(agentnum custnum magic status classnum custom censustract)) { -<INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>"> -% } +% for my $param ( +% qw( +% agentnum cust_status cust_main_salesnum salesnum custnum magic status +% custom pkgbatch zip +% 477part 477rownum date +% report_option +% ), +% grep { /^location_\w+$/ || /^report_option_any/ } $cgi->param +% ) { + <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>"> +% } +% +% for my $param (qw( censustract censustract2 ) ) { +% next unless grep { $_ eq $param } $cgi->param; + <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>"> +% } % -% foreach my $pkgpart ($cgi->param('pkgpart')) { -<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart |h %>"> +% for my $param (qw( pkgpart classnum refnum towernum )) { +% foreach my $value ($cgi->param($param)) { + <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $value |h %>"> +% } % } % -% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) { +% foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { % + <INPUT TYPE="hidden" NAME="<% $field %>_null" VALUE="<% $cgi->param("${field}_null") |h %>"> <INPUT TYPE="hidden" NAME="<% $field %>begin" VALUE="<% $cgi->param("${field}.begin") |h %>"> <INPUT TYPE="hidden" NAME="<% $field %>beginning" VALUE="<% $cgi->param("${field}beginning") |h %>"> <INPUT TYPE="hidden" NAME="<% $field %>end" VALUE="<% $cgi->param("${field}.end") |h %>"> diff --git a/httemplate/misc/bulk_pkg_increment_bill.cgi b/httemplate/misc/bulk_pkg_increment_bill.cgi index d594b55..fc9bbc8 100755 --- a/httemplate/misc/bulk_pkg_increment_bill.cgi +++ b/httemplate/misc/bulk_pkg_increment_bill.cgi @@ -10,16 +10,32 @@ %# some false laziness w/search/cust_pkg.cgi <INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords |h %>"> -% for my $param (qw(agentnum custnum magic status classnum custom censustract)) { -<INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>"> -% } +% for my $param ( +% qw( +% agentnum cust_status cust_main_salesnum salesnum custnum magic status +% custom pkgbatch zip +% 477part 477rownum date +% report_option +% ), +% grep { /^location_\w+$/ || /^report_option_any/ } $cgi->param +% ) { + <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>"> +% } +% +% for my $param (qw( censustract censustract2 ) ) { +% next unless grep { $_ eq $param } $cgi->param; + <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>"> +% } % -% foreach my $pkgpart ($cgi->param('pkgpart')) { -<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart |h %>"> +% for my $param (qw( pkgpart classnum refnum towernum )) { +% foreach my $value ($cgi->param($param)) { + <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $value |h %>"> +% } % } % -% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) { +% foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { % + <INPUT TYPE="hidden" NAME="<% $field %>_null" VALUE="<% $cgi->param("${field}_null") |h %>"> <INPUT TYPE="hidden" NAME="<% $field %>begin" VALUE="<% $cgi->param("${field}.begin") |h %>"> <INPUT TYPE="hidden" NAME="<% $field %>beginning" VALUE="<% $cgi->param("${field}beginning") |h %>"> <INPUT TYPE="hidden" NAME="<% $field %>end" VALUE="<% $cgi->param("${field}.end") |h %>"> diff --git a/httemplate/misc/process/bulk_change_pkg.cgi b/httemplate/misc/process/bulk_change_pkg.cgi index e22dafe..2432f3c 100755 --- a/httemplate/misc/process/bulk_change_pkg.cgi +++ b/httemplate/misc/process/bulk_change_pkg.cgi @@ -11,16 +11,43 @@ my %search_hash = (); $search_hash{'query'} = $cgi->param('query'); -for my $param (qw(agentnum magic status classnum pkgpart)) { - $search_hash{$param} = $cgi->param($param) - if $cgi->param($param); +#scalars +for (qw( agentnum cust_status cust_main_salesnum salesnum custnum magic status + custom cust_fields pkgbatch zip + 477part 477rownum date + )) +{ + $search_hash{$_} = $cgi->param($_) if length($cgi->param($_)); +} + +#arrays +for my $param (qw( pkgpart classnum refnum towernum )) { + $search_hash{$param} = [ $cgi->param($param) ] + if grep { $_ eq $param } $cgi->param; +} + +#scalars that need to be passed if empty +for my $param (qw( censustract censustract2 )) { + $search_hash{$param} = $cgi->param($param) || '' + if grep { $_ eq $param } $cgi->param; +} + +#location flags (checkboxes) +my @loc = grep /^\w+$/, $cgi->param('loc'); +$search_hash{"location_$_"} = 1 foreach @loc; + +my $report_option = $cgi->param('report_option'); +$search_hash{report_option} = $report_option if $report_option; + +for my $param (grep /^report_option_any/, $cgi->param) { + $search_hash{$param} = $cgi->param($param); } ### # parse dates ### -#false laziness w/report_cust_pkg.html +#false laziness w/report_cust_pkg.html and bulk_pkg_increment_bill.cgi my %disable = ( 'all' => {}, 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, }, @@ -30,7 +57,9 @@ my %disable = ( '' => {}, ); -foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) { +foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { + + $search_hash{$field.'_null'} = scalar( $cgi->param($field.'_null') ); my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); diff --git a/httemplate/misc/process/bulk_pkg_increment_bill.cgi b/httemplate/misc/process/bulk_pkg_increment_bill.cgi index d89f491..48c9de7 100755 --- a/httemplate/misc/process/bulk_pkg_increment_bill.cgi +++ b/httemplate/misc/process/bulk_pkg_increment_bill.cgi @@ -25,17 +25,43 @@ my %search_hash = (); $search_hash{'query'} = $cgi->param('query'); -for my $param (qw(agentnum magic status classnum pkgpart)) { - $search_hash{$param} = $cgi->param($param) - if $cgi->param($param); +#scalars +for (qw( agentnum cust_status cust_main_salesnum salesnum custnum magic status + custom cust_fields pkgbatch zip + 477part 477rownum date + )) +{ + $search_hash{$_} = $cgi->param($_) if length($cgi->param($_)); +} + +#arrays +for my $param (qw( pkgpart classnum refnum towernum )) { + $search_hash{$param} = [ $cgi->param($param) ] + if grep { $_ eq $param } $cgi->param; +} + +#scalars that need to be passed if empty +for my $param (qw( censustract censustract2 )) { + $search_hash{$param} = $cgi->param($param) || '' + if grep { $_ eq $param } $cgi->param; +} + +#location flags (checkboxes) +my @loc = grep /^\w+$/, $cgi->param('loc'); +$search_hash{"location_$_"} = 1 foreach @loc; + +my $report_option = $cgi->param('report_option'); +$search_hash{report_option} = $report_option if $report_option; + +for my $param (grep /^report_option_any/, $cgi->param) { + $search_hash{$param} = $cgi->param($param); } ### # parse dates ### -#false laziness w/report_cust_pkg.html -# and, now, w/bulk_change_pkg.cgi +#false laziness w/report_cust_pkg.html and bulk_change_pkg.cgi my %disable = ( 'all' => {}, 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, }, @@ -45,7 +71,9 @@ my %disable = ( '' => {}, ); -foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) { +foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { + + $search_hash{$field.'_null'} = scalar( $cgi->param($field.'_null') ); my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html index 7e33e15..cb20712 100755 --- a/httemplate/misc/process/cancel_pkg.html +++ b/httemplate/misc/process/cancel_pkg.html @@ -1,4 +1,4 @@ -<& /elements/popup-topreload.html, et("Package $past_method") &> +<& /elements/popup-topreload.html, emt("Package $past_method") &> <%once> my %past = ( 'cancel' => 'cancelled', diff --git a/httemplate/search/cust_credit_bill_pkg.html b/httemplate/search/cust_credit_bill_pkg.html index 0cdd8de..4a14893 100644 --- a/httemplate/search/cust_credit_bill_pkg.html +++ b/httemplate/search/cust_credit_bill_pkg.html @@ -457,6 +457,8 @@ if ( $cgi->param('nottax') ) { push @where, "billpkgtaxratelocationnum IS NULL"; } + $join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum ) '; + $join_pkg .= ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) '; } elsif ( $conf->exists('tax-pkg_address') ) { diff --git a/httemplate/search/cust_msg.html b/httemplate/search/cust_msg.html index 33e1815..65460f7 100644 --- a/httemplate/search/cust_msg.html +++ b/httemplate/search/cust_msg.html @@ -19,7 +19,10 @@ ucfirst($_[0]->msgtype) || $_[0]->msgname }, sub { - join('<BR>', split(/,\s*/, $_[0]->env_to) ) + join('<BR>', + map { encode_entities($_->format) } + Email::Address->parse($_[0]->env_to) + ) }, 'status', sub { encode_entities($_[0]->error) }, diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index dbd346d..df1d7e5 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -203,6 +203,8 @@ my %disable = ( foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { + $search_hash{$field.'_null'} = scalar( $cgi->param($field.'_null') ); + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); next if $beginning == 0 && $ending == 4294967295 diff --git a/httemplate/search/elements/grouped-search/core b/httemplate/search/elements/grouped-search/core index 3d38a8c..b15fe86 100644 --- a/httemplate/search/elements/grouped-search/core +++ b/httemplate/search/elements/grouped-search/core @@ -110,7 +110,7 @@ for my $i (0 .. scalar(@groups) - 1) { push @group_labels, $label; my @footer; - if ($opt{'subtotal_row'}) { + if ($opt{'subtotal_row'} and @groups > 1) { for( my $col = 0; exists($opt{'subtotal_row'}[$col]) or exists($opt{'header'}[$col]); $col++ diff --git a/httemplate/search/elements/grouped-search/html b/httemplate/search/elements/grouped-search/html index 9c2418a..28d0040 100644 --- a/httemplate/search/elements/grouped-search/html +++ b/httemplate/search/elements/grouped-search/html @@ -106,14 +106,18 @@ if ($group_info->{num} > 1) { &> <DIV CLASS="fstabcontainer"> +% if ( $group->num_rows > 0 ) { +<P><% emt('[quant,_1,_2]', $group->num_rows, $opt{name_singular}) %> +</P> %# download links -<P><% emt('Download full results') %><BR> +<P><% emt('Download results:') %> % $cgi->param('type', 'xls'); -<A HREF="<% $cgi->self_url %>"><% emt('as Excel spreadsheet') %></A><BR> +<A HREF="<% $cgi->self_url %>"><% emt('Spreadsheet') %></A> | % $cgi->param('type', 'html-print'); -<A HREF="<% $cgi->self_url %>"><% emt('as printable copy') %></A><BR> +<A HREF="<% $cgi->self_url %>"><% emt('webpage') %></A> % $cgi->delete('type'); </P> +% } <% $pager %> diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html index 27aecec..556177c 100755 --- a/httemplate/search/report_cust_pkg.html +++ b/httemplate/search/report_cust_pkg.html @@ -80,6 +80,7 @@ what.form.<% $field %>_beginning_text.disabled = true; what.form.<% $field %>_ending_text.disabled = true; + what.form.<% $field %>_null.disabled = true; what.form.<% $field %>_beginning_text.style.backgroundColor = '#dddddd'; what.form.<% $field %>_ending_text.style.backgroundColor = '#dddddd'; @@ -90,15 +91,21 @@ % } else { - what.form.<% $field %>_beginning_text.disabled = false; - what.form.<% $field %>_ending_text.disabled = false; - what.form.<% $field %>_beginning_text.style.backgroundColor = '#ffffff'; - what.form.<% $field %>_ending_text.style.backgroundColor = '#ffffff'; + what.form.<% $field %>_null.disabled = false; - what.form.<% $field %>_beginning_button.style.display = ''; - what.form.<% $field %>_ending_button.style.display = ''; - what.form.<% $field %>_beginning_disabled.style.display = 'none'; - what.form.<% $field %>_ending_disabled.style.display = 'none'; + if ( ! what.form.<% $field %>_null.checked ) { + + what.form.<% $field %>_beginning_text.disabled = false; + what.form.<% $field %>_ending_text.disabled = false; + what.form.<% $field %>_beginning_text.style.backgroundColor = '#ffffff'; + what.form.<% $field %>_ending_text.style.backgroundColor = '#ffffff'; + + what.form.<% $field %>_beginning_button.style.display = ''; + what.form.<% $field %>_ending_button.style.display = ''; + what.form.<% $field %>_beginning_disabled.style.display = 'none'; + what.form.<% $field %>_ending_disabled.style.display = 'none'; + + } % } % } @@ -109,6 +116,37 @@ } +% foreach my $field (@date_fields) { + + function <% $field %>_null_changed(what) { + + if ( what.checked ) { + what.form.<% $field %>_beginning_text.disabled = true; + what.form.<% $field %>_ending_text.disabled = true; + what.form.<% $field %>_beginning_text.style.backgroundColor = '#dddddd'; + what.form.<% $field %>_ending_text.style.backgroundColor = '#dddddd'; + what.form.<% $field %>_beginning_button.style.display = 'none'; + what.form.<% $field %>_ending_button.style.display = 'none'; + what.form.<% $field %>_beginning_disabled.style.display = ''; + what.form.<% $field %>_ending_disabled.style.display = ''; + + } else { + what.form.<% $field %>_beginning_text.disabled = false; + what.form.<% $field %>_ending_text.disabled = false; + what.form.<% $field %>_beginning_text.style.backgroundColor = '#ffffff'; + what.form.<% $field %>_ending_text.style.backgroundColor = '#ffffff'; + + what.form.<% $field %>_beginning_button.style.display = ''; + what.form.<% $field %>_ending_button.style.display = ''; + what.form.<% $field %>_beginning_disabled.style.display = 'none'; + what.form.<% $field %>_ending_disabled.style.display = 'none'; + + } + + } + +% } + </SCRIPT> <& /elements/tr-select-pkg_class.html, @@ -135,6 +173,7 @@ <TD></TD> <TD>From date <i>(m/d/y)</i></TD> <TD>To date <i>(m/d/y)</i></TD> + <TD>Empty date</TD> </TR> % my $noinit = 0; % foreach my $field (@date_fields) { @@ -152,6 +191,13 @@ </TD> % $noinit = 1; % } + <TD ALIGN="center"> + <& /elements/checkbox.html, + 'field' => $field.'_null', + 'value' => 'Y', + 'onchange' => $field.'_null_changed', + &> + </TD> </TR> % } #foreach $field </TABLE> diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html index 14f7fb0..dd15c7b 100644 --- a/httemplate/view/cust_main/packages/package.html +++ b/httemplate/view/cust_main/packages/package.html @@ -268,7 +268,7 @@ && ! $cust_pkg->get('cancel') && $can_discount_pkg }, - popup => "edit/cust_pkg_discount.html?$plink". + popup => "edit/cust_pkg_discount.html?$plink", actionlabel => emt('Discount package'), width => 616, }, diff --git a/httemplate/view/cust_msg.html b/httemplate/view/cust_msg.html index 91a08eb..d2b043c 100755 --- a/httemplate/view/cust_msg.html +++ b/httemplate/view/cust_msg.html @@ -61,7 +61,9 @@ $custmsgnum =~ /^(\d+)$/ or die "illegal custmsgnum"; my $cust_msg = qsearchs('cust_msg', { 'custmsgnum' => $custmsgnum }); my $date = ''; $date = time2str('%Y-%m-%d %T', $cust_msg->_date) if ( $cust_msg->_date ); -my $env_to = join('</TD></TR><TR><TD></TD><TD>', split(',', $cust_msg->env_to)); +my @to = map { encode_entities($_->format) } + Email::Address->parse($cust_msg->env_to); +my $env_to = join('</TD></TR><TR><TD></TD><TD>', @to); my %label = ( 'sent' => 'Sent:', diff --git a/httemplate/view/directions.html b/httemplate/view/directions.html index 8377d12..1c99cda 100644 --- a/httemplate/view/directions.html +++ b/httemplate/view/directions.html @@ -62,7 +62,8 @@ function show_route() { if ( status == google.maps.DirectionsStatus.OK ) { directionsDisplay.setDirections(result); } else { - document.body.innerHTML = ('<P STYLE="color: red;">Directions lookup failed with the following error: '+status+'</P>'); + document.body.innerHTML = ('<P STYLE="color: red;">Directions lookup failed with the following error: '+status+'</P>') + + <% include('/elements/google_maps_api_key.html' ) |js_string%>; } }); } diff --git a/ng_selfservice/elements/add_password_validation.php b/ng_selfservice/elements/add_password_validation.php new file mode 100644 index 0000000..6938437 --- /dev/null +++ b/ng_selfservice/elements/add_password_validation.php @@ -0,0 +1,51 @@ +<SCRIPT> +function add_password_validation (fieldid,nologin) { + var inputfield = document.getElementById(fieldid); + inputfield.onchange = function () { + var fieldid = this.id+'_result'; + var resultfield = document.getElementById(fieldid); + var svcnum = ''; + var svcfield = document.getElementById(this.id+'_svcnum'); + if (svcfield) { + svcnum = svcfield.options[svcfield.selectedIndex].value; + } + if (this.value) { + resultfield.innerHTML = '<SPAN STYLE="color: blue;">Validating password...</SPAN>'; + var validate_data = { + fieldid: fieldid, + check_password: this.value, + }; + if (!nologin) { + validate_data['svcnum'] = svcnum; + } + $.ajax({ + url: 'xmlrpc_validate_passwd.php', + data: validate_data, + method: 'POST', + success: function ( result ) { + result = JSON.parse(result); + var resultfield = document.getElementById(fieldid); + if (resultfield) { + var errorimg = '<IMG SRC="images/error.png" style="width: 1em; display: inline-block; padding-right: .5em">'; + var validimg = '<IMG SRC="images/tick.png" style="width: 1em; display: inline-block; padding-right: .5em">'; + if (result.password_valid) { + resultfield.innerHTML = validimg+'<SPAN STYLE="color: green;">Password valid!</SPAN>'; + } else if (result.password_invalid) { + resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.password_invalid+'</SPAN>'; + } else { + resultfield.innerHTML = ''; + } + } + }, + error: function ( jqXHR, textStatus, errorThrown ) { + var resultfield = document.getElementById(fieldid); + console.log('ajax error: '+textStatus+'+'+errorThrown); + if (resultfield) { + resultfield.innerHTML = ''; + } + }, + }); + } + }; +} +</SCRIPT> diff --git a/ng_selfservice/images/error.png b/ng_selfservice/images/error.png Binary files differnew file mode 100644 index 0000000..628cf2d --- /dev/null +++ b/ng_selfservice/images/error.png diff --git a/ng_selfservice/images/tick.png b/ng_selfservice/images/tick.png Binary files differnew file mode 100644 index 0000000..a9925a0 --- /dev/null +++ b/ng_selfservice/images/tick.png diff --git a/ng_selfservice/password.php b/ng_selfservice/password.php index 41296ed..a6e6795 100644 --- a/ng_selfservice/password.php +++ b/ng_selfservice/password.php @@ -1,5 +1,92 @@ <? $title ='Change Password'; include('elements/header.php'); ?> <? $current_menu = 'password.php'; include('elements/menu.php'); ?> -Chagne password +<? +$error = ''; +$pwd_change_success = false; +if ( isset($_POST['svcnum']) ) { + + $pwd_change_result = $freeside->myaccount_passwd(array( + 'session_id' => $_COOKIE['session_id'], + 'svcnum' => $_POST['svcnum'], + 'new_password' => $_POST['new_password'], + 'new_password2' => $_POST['new_password2'], + )); + + if ($pwd_change_result['error']) { + $error = $pwd_change_result['error']; + } else { + $pwd_change_success = true; + } +} + +if ($pwd_change_success) { +?> + +<P>Password changed for <? echo $pwd_change_result['value'],' ',$pwd_change_result['label'] ?>.</P> + +<? +} else { + $pwd_change_svcs = $freeside->list_svcs(array( + 'session_id' => $_COOKIE['session_id'], + 'svcdb' => 'svc_acct', + )); + if (isset($pwd_change_svcs['error'])) { + $error = $error || $pwd_change_svcs['error']; + } + if (!isset($pwd_change_svcs['svcs'])) { + $pwd_change_svcs['svcs'] = $pwd_change_svcs['svcs']; + $error = $error || 'Unknown error loading services'; + } + if ($error) { + include('elements/error.php'); + } +?> + +<FORM METHOD="POST"> +<TABLE BGCOLOR="#cccccc"> + <TR> + <TH ALIGN="right">Change password for account: </TH> + <TD> + <SELECT ID="new_password_svcnum" NAME="svcnum"> +<? + $selected_svcnum = isset($_POST['svcnum']) ? $_POST['svcnum'] : $pwd_change_svcs['svcnum']; + foreach ($pwd_change_svcs['svcs'] as $svc) { +?> + <OPTION VALUE="<? echo $svc['svcnum'] ?>"<? echo $selected_svcnum == $svc['svcnum'] ? ' SELECTED' : '' ?>> + <? echo $svc['label'],': ',$svc['value'] ?> + </OPTION> +<? + } +?> + </SELECT> + </TD> + </TR> + + <TR> + <TH ALIGN="right">New password: </TH> + <TD> + <INPUT ID="new_password" TYPE="password" NAME="new_password" SIZE="18"> + <DIV ID="new_password_result"></DIV> +<? include('elements/add_password_validation.php'); ?> + <SCRIPT>add_password_validation('new_password');</SCRIPT> + </TD> + </TR> + + <TR> + <TH ALIGN="right">Re-enter new password: </TH> + <TD><INPUT TYPE="password" NAME="new_password2" SIZE="18"></TD> + </TR> + +</TABLE> +<BR> + +<INPUT TYPE="submit" VALUE="Change password"> + +</FORM> + +<? +} // end if $pwd_change_show_form +?> + <? include('elements/menu_footer.php'); ?> <? include('elements/footer.php'); ?> diff --git a/ng_selfservice/xmlrpc_validate_passwd.php b/ng_selfservice/xmlrpc_validate_passwd.php new file mode 100644 index 0000000..5632dc3 --- /dev/null +++ b/ng_selfservice/xmlrpc_validate_passwd.php @@ -0,0 +1,15 @@ +<? + +require_once('elements/session.php'); + +$xmlrpc_args = array( + fieldid => $_POST['fieldid'], + check_password => $_POST['check_password'], + svcnum => $_POST['svcnum'], + session_id => $_COOKIE['session_id'] +); + +$result = $freeside->validate_passwd($xmlrpc_args); +echo json_encode($result); + +?> |