diff options
| author | Ivan Kohler <ivan@freeside.biz> | 2018-09-13 10:54:53 -0700 |
|---|---|---|
| committer | Ivan Kohler <ivan@freeside.biz> | 2018-09-13 10:54:53 -0700 |
| commit | d7b4beaeb975c55f5203aef275c7c01853fa51f2 (patch) | |
| tree | 2eb0a708bfbfdbfaa3c6db935911a8fd830446d4 | |
| parent | 2c0ab6236b187a39fbb6e2046180e2531a391333 (diff) | |
| parent | 4cf578816f5a491b1f66ddb5a9e832457ebe1332 (diff) | |
Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE_3_BRANCH
31 files changed, 486 insertions, 136 deletions
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 9f9b1e2fc..5048e4407 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2999,6 +2999,60 @@ sub ut_enumn { : ''; } +=item ut_date COLUMN + +Check/untaint a column containing a date string. + +Date will be normalized to YYYY-MM-DD format + +=cut + +sub ut_date { + my ( $self, $field ) = @_; + my $value = $self->getfield( $field ); + + my @date = split /[\-\/]/, $value; + if ( scalar(@date) == 3 ) { + @date = @date[2,0,1] if $date[2] >= 1900; + + local $@; + my $ymd; + eval { + # DateTime will die given invalid date + $ymd = DateTime->new( + year => $date[0], + month => $date[1], + day => $date[2], + )->ymd('-'); + }; + + unless( $@ ) { + $self->setfield( $field, $ymd ) unless $value eq $ymd; + return ''; + } + + } + return "Illegal (date) field $field: $value"; +} + +=item ut_daten COLUMN + +Check/untaint a column containing a date string. + +Column may be null. + +Date will be normalized to YYYY-MM-DD format + +=cut + +sub ut_daten { + my ( $self, $field ) = @_; + + $self->getfield( $field ) =~ /^()$/ + ? $self->setfield( $field, '' ) + : $self->ut_date( $field ); +} + =item ut_flag COLUMN Check/untaint a column if it contains either an empty string or 'Y'. This diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm index cef7813af..7c4f97309 100644 --- a/FS/FS/Report/Table.pm +++ b/FS/FS/Report/Table.pm @@ -1115,7 +1115,7 @@ sub calculate_churn_cust { as suspended, SUM((s_active = 0 and s_suspended > 0 and e_active > 0)::int) as resumed, - SUM((s_active > 0 and e_active = 0 and e_suspended = 0)::int) + SUM((e_active = 0 and e_cancelled > s_cancelled)::int) as cancelled FROM ($cust_sql) AS x "; diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm index 4db3cdfd1..8f6b6a3b5 100644 --- a/FS/FS/contact.pm +++ b/FS/FS/contact.pm @@ -131,7 +131,6 @@ sub insert { my $dbh = dbh; my $error = $self->SUPER::insert; - $error ||= $self->insert_password_history; if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -191,6 +190,15 @@ sub insert { } } + if ( $self->get('password') ) { + my $error = $self->is_password_allowed($self->get('password')) + || $self->change_password($self->get('password')); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -614,7 +622,7 @@ sub authenticate_password { $hash eq $check_hash; - } else { + } else { return 0 if $self->_password eq ''; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 621f3d144..b103996a4 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2145,6 +2145,10 @@ sub check { if !$import && !$ignore_expired_card && ( $y<$nowy || ( $y==$nowy && $1<$nowm ) ); + + if ( my $error = $self->ut_daten('paydate') ) { + return $error; + } } if ( $self->payname eq '' && $self->payby !~ /^(CHEK|DCHK)$/ && @@ -5643,8 +5647,86 @@ sub _upgrade_data { #class method FS::Setup::enable_encryption(); } + $class->_upgrade_data_paydate_edgebug; } +=item _upgrade_data_paydate_edgebug + +Correct bad data injected into payment expire date column by Edge browser bug + +The month and year values may have an extra character injected into form POST +data by Edge browser. It was possible for some bad month values to slip +past data validation. + +If the stored value was out of range, it was causing payments screen to crash. +We can detect and fix this by dropping the second digit. + +If the stored value is is 11 or 12, it's possible the user inputted a 1. In +this case, the payment method will fail to authorize, but the record will +not cause crashdumps for being out of range. + +In short, check for any expiration month > 12, and drop the extra digit + +=cut + +sub _upgrade_data_paydate_edgebug { + my $journal_label = 'cust_main_paydate_edgebug'; + return if FS::upgrade_journal->is_done( $journal_label ); + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + + for my $row ( + FS::Record::qsearch( + cust_main => { paydate => { op => '!=', value => '' }} + ) + ) { + next unless $row->ut_daten('paydate'); + + # paydate column stored in database has failed date validation + my $bad_paydate = $row->paydate; + + my @date = split /[\-\/]/, $bad_paydate; + @date = @date[2,0,1] if $date[2] > 1900; + + # Only autocorrecting when month > 12 - notify operator + unless ( $date[1] > 12 ) { + die sprintf( + 'Unable to correct bad paydate stored in cust_main row '. + 'custnum(%s) paydate(%s)', + $row->custnum, + $bad_paydate, + ); + } + + $date[1] = substr( $date[1], 0, 1 ); + $row->paydate( join('-', @date )); + + if ( my $error = $row->replace ) { + die sprintf( + 'Failed to autocorrect bad paydate stored in cust_main row '. + 'custnum(%s) paydate(%s) - error: %s', + $row->custnum, + $bad_paydate, + $error + ); + } + + warn sprintf( + 'Autocorrected bad paydate stored in cust_main row '. + "custnum(%s) old-paydate(%s) new-paydate(%s)\n", + $row->custnum, + $bad_paydate, + $row->paydate, + ); + + } + + FS::upgrade_journal->set_done( $journal_label ); + dbh->commit unless $oldAutoCommit; +} + + sub queueable_upgrade { my $class = shift; diff --git a/FS/FS/deploy_zone.pm b/FS/FS/deploy_zone.pm index 6ad355f72..7c3a76ca5 100644 --- a/FS/FS/deploy_zone.pm +++ b/FS/FS/deploy_zone.pm @@ -6,7 +6,7 @@ use FS::Record qw( qsearch qsearchs dbh ); use Storable qw(thaw); use MIME::Base64; -use JSON qw(encode_json decode_json) ; +use Cpanel::JSON::XS qw(encode_json decode_json); use LWP::UserAgent; use HTTP::Request::Common; diff --git a/FS/FS/part_export/saisei.pm b/FS/FS/part_export/saisei.pm index af77dfa78..6a42b94ba 100644 --- a/FS/FS/part_export/saisei.pm +++ b/FS/FS/part_export/saisei.pm @@ -201,12 +201,28 @@ sub _export_insert { my $accesspoint = process_sector($self, $sector_opt); return $self->api_error if $self->{'__saisei_error'}; +## get custnum and pkgpart from cust_pkg for virtual access point + my $cust_pkg = FS::Record::qsearchs({ + 'table' => 'cust_pkg', + 'hashref' => { 'pkgnum' => $svc_broadband->{Hash}->{pkgnum}, }, + }); + my $virtual_ap_name = $cust_pkg->{Hash}->{custnum}.'_'.$cust_pkg->{Hash}->{pkgpart}.'_'.$svc_broadband->{Hash}->{speed_down}.'_'.$svc_broadband->{Hash}->{speed_up}; + + my $virtual_ap_opt = { + 'virtual_name' => $virtual_ap_name, + 'sector_name' => $sector_name, + 'virtual_uprate_limit' => $svc_broadband->{Hash}->{speed_up}, + 'virtual_downrate_limit' => $svc_broadband->{Hash}->{speed_down}, + }; + my $virtual_ap = process_virtual_ap($self, $virtual_ap_opt); + return $self->api_error if $self->{'__saisei_error'}; + ## tie host to user add sector name as access point. $self->api_add_host_to_user( $user->{collection}->[0]->{name}, $rateplan->{collection}->[0]->{name}, $svc_broadband->{Hash}->{ip_addr}, - $accesspoint->{collection}->[0]->{name}, + $virtual_ap->{collection}->[0]->{name}, ) unless $self->{'__saisei_error'}; } @@ -216,8 +232,8 @@ sub _export_insert { sub _export_replace { my ($self, $svc_broadband) = @_; - $self->_export_insert($svc_broadband); - return ''; + my $error = $self->_export_insert($svc_broadband); + return $error; } sub _export_delete { @@ -805,6 +821,44 @@ sub process_sector { return $accesspoint; } +sub process_virtual_ap { + my ($self, $opt) = @_; + + my $existing_virtual_ap; + my $virtual_name = $opt->{virtual_name}; + + #check if sector has been set up as an access point. + $existing_virtual_ap = $self->api_get_accesspoint($virtual_name); + + # modify the existing virtual accesspoint if changing it. this should never happen + $self->api_modify_existing_accesspoint ( + $virtual_name, + $opt->{sector_name}, + $opt->{virtual_uprate_limit}, + $opt->{virtual_downrate_limit}, + ) if $existing_virtual_ap && $opt->{modify_existing}; + + #if virtual ap does not exist as an access point create it. + $self->api_create_accesspoint( + $virtual_name, + $opt->{virtual_uprate_limit}, + $opt->{virtual_downrate_limit}, + ) unless $existing_virtual_ap; + +my $update_sector; +if ($existing_virtual_ap && ($existing_virtual_ap->{collection}->[0]->{uplink}->{link}->{name} ne $opt->{sector_name})) { + $update_sector = 1; +} + + # Attach newly created virtual ap to tower sector ap or if sector has changed. + $self->api_modify_accesspoint($virtual_name, $opt->{sector_name}) unless ($self->{'__saisei_error'} || ($existing_virtual_ap && !$update_sector)); + + # set access point to existing one or newly created one. + my $accesspoint = $existing_virtual_ap ? $existing_virtual_ap : $self->api_get_accesspoint($virtual_name); + + return $accesspoint; +} + sub export_provisioned_services { my $job = shift; my $param = thaw(decode_base64(shift)); diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 11e3331a0..9cd085942 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -156,8 +156,8 @@ sub table_info { disable_inventory => 1, }, 'serviceid' => 'Torrus serviceid', #but is should be hidden - 'speed_test_up' => 'Speed test download (Kbps)', - 'speed_test_down' => 'Speed test upload (Kbps)', + 'speed_test_up' => { 'label' => 'Speed test upload (Kbps)' }, + 'speed_test_down' => { 'label' => 'Speed test download (Kbps)' }, 'speed_test_latency' => 'Speed test latency (ms)', }, }; @@ -364,6 +364,8 @@ sub check { || $self->ut_textn('description') || $self->ut_numbern('speed_up') || $self->ut_numbern('speed_down') + || $self->ut_numbern('speed_test_up') + || $self->ut_numbern('speed_test_down') || $self->ut_ipn('ip_addr') || $self->ut_hexn('mac_addr') || $self->ut_hexn('auth_key') diff --git a/bin/move_svc_broadband_speeds.pl b/bin/move_svc_broadband_speeds.pl new file mode 100755 index 000000000..7d20ef68a --- /dev/null +++ b/bin/move_svc_broadband_speeds.pl @@ -0,0 +1,66 @@ +#!/usr/bin/perl + +use strict; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearchs qsearch); +use FS::svc_broadband; + +my $user = shift or die &usage; +my $dbh = adminsuidsetup($user); + +my $fcc_up_speed = "(select part_pkg_fcc_option.optionvalue from part_pkg_fcc_option where fccoptionname = 'broadband_upstream' and pkgpart = cust_pkg.pkgpart) AS fcc477_upstream"; +my $fcc_down_speed = "(select part_pkg_fcc_option.optionvalue from part_pkg_fcc_option where fccoptionname = 'broadband_downstream' and pkgpart = cust_pkg.pkgpart) AS fcc477_downstream"; + +foreach my $rec (qsearch({ + 'select' => 'svc_broadband.*, cust_svc.svcpart, cust_pkg.pkgpart, '.$fcc_up_speed.', '.$fcc_down_speed, + 'table' => 'svc_broadband', + 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) LEFT JOIN cust_pkg USING ( pkgnum )', +})) { + $rec->{Hash}->{speed_test_up} = $rec->{Hash}->{speed_up} ? $rec->{Hash}->{speed_up} : "null"; + $rec->{Hash}->{speed_test_down} = $rec->{Hash}->{speed_down} ? $rec->{Hash}->{speed_down} : "null"; + $rec->{Hash}->{speed_up} = $rec->{Hash}->{fcc477_upstream} ? $rec->{Hash}->{fcc477_upstream} * 1000 : "null"; + $rec->{Hash}->{speed_down} = $rec->{Hash}->{fcc477_downstream} ? $rec->{Hash}->{fcc477_downstream} * 1000 : "null"; + + my $sql = "UPDATE svc_broadband set + speed_up = $rec->{Hash}->{speed_up}, + speed_down = $rec->{Hash}->{speed_down}, + speed_test_up = $rec->{Hash}->{speed_test_up}, + speed_test_down = $rec->{Hash}->{speed_test_down} + WHERE svcnum = $rec->{Hash}->{svcnum}"; + + warn "Fixing broadband service speeds for service ".$rec->{Hash}->{svcnum}."-".$rec->{Hash}->{description}."\n"; + + my $sth = $dbh->prepare($sql) or die $dbh->errstr; + $sth->execute or die $sth->errstr; + +} + +$dbh->commit; + +warn "Completed fixing broadband service speeds!\n"; + +exit; + +=head1 NAME + +move_svc_broadband_speeds + +=head1 SYNOPSIS + + move_svc_broadband_speeds.pl [ user ] + +=head1 DESCRIPTION + +Moves value for speed_down to speed_test_down, speed_up to speed_test_up, +and sets speed_down, speed_up to matching fcc_477 speeds from package for +all svc_broadband services. + +user: freeside username + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::svc_broadband> + +=cut
\ No newline at end of file diff --git a/httemplate/edit/cust_main-contacts.html b/httemplate/edit/cust_main-contacts.html index 7dedfe04d..b046c5b18 100644 --- a/httemplate/edit/cust_main-contacts.html +++ b/httemplate/edit/cust_main-contacts.html @@ -1,12 +1,27 @@ -<% include('elements/edit.html', - 'name_singular' => 'customer contacts', #yes, we're editing all of them - 'table' => 'cust_main', - 'post_url' => popurl(1). 'process/cust_main-contacts.html', - 'labels' => { 'custnum' => ' ', #XXX supress this line entirely, its being redundant - 'contactnum' => ' ', #'Contact', - #'locationnum' => ' ', - }, - 'fields' => [ +<SCRIPT> + function checkPasswordValidation(fieldid) { + var validationResult = document.getElementById(fieldid+'_result').innerHTML; + if (validationResult.match(/Password valid!/)) { + return true; + } + else { + return false; + } + } +</SCRIPT> + +<& '/elements/validate_password_js.html', &> + +<& elements/edit.html, + 'name_singular' => 'customer contacts', #yes, we're editing all of them + 'table' => 'cust_main', + 'post_url' => popurl(1). 'process/cust_main-contacts.html', + 'no_pkey_display' => 1, + 'labels' => { 'custnum' => ' ', #XXX supress this line entirely, its being redundant + 'contactnum' => ' ', #'Contact', + #'locationnum' => ' ', + }, + 'fields' => [ { 'field' => 'contactnum', 'type' => 'contact', 'colspan' => 6, @@ -30,13 +45,19 @@ $conf->config('countrydefault') || 'US', 1, #no balance ), - ) -%> +&> <%init> my $curuser = $FS::CurrentUser::CurrentUser; my $conf = new FS::Conf; +if ( $cgi->param('redirect') ) { + my $session = $cgi->param('redirect'); + my $pref = $curuser->option("redirect$session"); + die "unknown redirect session $session\n" unless length($pref); + $cgi = new CGI($pref); +} + my $custnum; if ( $cgi->param('error') ) { $custnum = scalar($cgi->param('custnum')); diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 3826f9752..ff1da4945 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -633,7 +633,7 @@ Example: var newrow = <% include(@layer_opt, html_only=>1) |js_string %>; % #until the rest have html/js_only -% if ( $type eq 'selectlayers' || $type =~ /^select-cgp_rule_/ ) { +% if ( $type eq 'selectlayers' || $type =~ /^select-cgp_rule_/ || $type eq 'contact') { var newfunc = <% include(@layer_opt, js_only=>1) |js_string %>; % } else { var newfunc = ''; diff --git a/httemplate/edit/process/cust_main-contacts.html b/httemplate/edit/process/cust_main-contacts.html index 10ec3638f..f51c796b4 100644 --- a/httemplate/edit/process/cust_main-contacts.html +++ b/httemplate/edit/process/cust_main-contacts.html @@ -1,6 +1,6 @@ <% include('elements/process.html', 'table' => 'cust_main', - 'error_redirect' => popurl(3). 'edit/cust_main-contacts.html?', + 'error_redirect' => popurl(3). 'edit/cust_main-contacts.html', 'agent_virt' => 1, 'skip_process' => 1, #we don't want to make any changes to cust_main 'process_o2m' => { diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index 1db3a9cd1..9e0fa5f55 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -172,7 +172,7 @@ my $update_svc = sub { foreach my $svc_part(@svcs) { my @part_svc_column = qsearch('part_svc_column',{ 'svcpart' => $svc_part->{Hash}->{svcpart}, 'columnflag' => 'P' }); - if ($svc_part->{Hash}->{svcdb} eq "svc_broadband" && (keys $args{fcc_options}) && @part_svc_column ) { + if ($svc_part->{Hash}->{svcdb} eq "svc_broadband" && (keys %{ $args{fcc_options} }) && @part_svc_column ) { ## find provisioned services to update my @svc_svcdb = qsearch({ 'table' => 'svc_broadband', diff --git a/httemplate/elements/change_password.html b/httemplate/elements/change_password.html index 7d95e19dc..65b7d8502 100644 --- a/httemplate/elements/change_password.html +++ b/httemplate/elements/change_password.html @@ -11,9 +11,9 @@ % if (!$opt{'no_label_display'}) { <A ID="<%$pre%>link" HREF="javascript:void(0)" onclick="<%$pre%>toggle(true)">(<% emt( $change_title ) %>)</A> % } -<DIV ID="<%$pre%>form" CLASS="passwordbox"> +<DIV ID="<%$pre%>div" CLASS="passwordbox"> % if (!$opt{'noformtag'}) { - <FORM METHOD="POST" ACTION="<%$fsurl%>misc/process/change-password.html" onsubmit="return checkPasswordValidation()"> + <FORM ID="<%$pre%>form" METHOD="POST" ACTION="<%$fsurl%>misc/process/change-password.html" onsubmit="return <%$pre%>checkPasswordValidation()"> % } <% $change_id_input %> @@ -44,11 +44,8 @@ function <%$pre%>toggle(toggle, clear) { if (clear) { document.getElementById('<%$pre%>password').value = ''; document.getElementById('<%$pre%>password_result').innerHTML = ''; -% if ($opt{'contact_num'}) { - document.getElementById('<% $opt{'pre_pwd_field_label'} %>selfservice_access').value = 'Y'; -% } } - document.getElementById('<%$pre%>form').style.display = + document.getElementById('<%$pre%>div').style.display = toggle ? 'inline-block' : 'none'; % if (!$opt{'no_label_display'}) { document.getElementById('<%$pre%>link').style.display = @@ -56,7 +53,7 @@ function <%$pre%>toggle(toggle, clear) { % } } -function checkPasswordValidation() { +function <%$pre%>checkPasswordValidation(resultId) { var validationResult = document.getElementById('<%$pre%>password_result').innerHTML; if (validationResult.match(/Password valid!/)) { return true; @@ -83,8 +80,8 @@ if ($opt{'svc_acct'}) { } elsif ($opt{'contact_num'}) { $change_id_input = ' - <INPUT TYPE="hidden" NAME="'.$opt{'pre_pwd_field_label'}.'contactnum" VALUE="' . $opt{'contact_num'} . '"> - <INPUT TYPE="hidden" NAME="'.$opt{'pre_pwd_field_label'}.'custnum" VALUE="' . $opt{'custnum'} . '"> + <INPUT TYPE="hidden" NAME="contactnum" VALUE="' . $opt{'contact_num'} . '"> + <INPUT TYPE="hidden" NAME="custnum" VALUE="' . $opt{'custnum'} . '"> '; $pre .= $opt{'pre_pwd_field_label'}; } diff --git a/httemplate/elements/city.html b/httemplate/elements/city.html index 4e9a60940..05250fef5 100644 --- a/httemplate/elements/city.html +++ b/httemplate/elements/city.html @@ -132,14 +132,14 @@ function <% $pre %>county_changed(what, callback) {} > % unless ( $opt{'disable_empty'} ) { - <OPTION VALUE="" <% $opt{city} eq '' ? 'SELECTED' : '' %>><% $opt{empty_label} %> + <OPTION VALUE="" <% $opt{city} eq '' ? 'SELECTED' : '' %>><% $opt{empty_label} %></OPTION> % } % foreach my $city ( @cities ) { <OPTION VALUE="<% $city |h %>" <% $city eq $opt{city} ? 'SELECTED' : '' %> - ><% $city eq $opt{empty_data_value} ? $opt{empty_data_label} : $city %> + ><% $city eq $opt{empty_data_value} ? $opt{empty_data_label} : $city %></OPTION> % } diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index 678193516..fef189e0c 100644 --- a/httemplate/elements/contact.html +++ b/httemplate/elements/contact.html @@ -1,4 +1,6 @@ -% unless ( $opt{'js_only'} ) { +% if ( $opt{'js_only'} ) { +<% $js %> +% } else { <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>"> @@ -40,6 +42,8 @@ % } % } elsif ( $field eq 'emailaddress' ) { % $value = join(', ', map $_->emailaddress, $contact->contact_email); +% } elsif ( $field eq 'password' ) { +% $value = $contact->get('_password') ? '********' : ''; % } else { % $value = $contact->get($field); % } @@ -51,28 +55,25 @@ ID = "<%$id%>_<%$field%>" STYLE = "width: 140px" > - <OPTION VALUE="">Disabled + <OPTION VALUE="" <% !$value ? 'SELECTED' : '' %>>Disabled % if ( $value || $self_base_url ) { <OPTION VALUE="<% $value eq 'Y' ? 'Y' : 'E' %>" <% $value eq 'Y' ? 'SELECTED' : '' %>>Enabled % if ( $value eq 'Y' && $self_base_url ) { <OPTION VALUE="R">Re-email - <OPTION VALUE="P"><% $pwd_change_label %> % } % } </SELECT> - <& /elements/change_password.html, - 'contact_num' => $curr_value, - 'custnum' => $opt{'custnum'}, - 'curr_value' => '', - 'no_label_display' => '1', - 'noformtag' => '1', - 'pre_pwd_field_label' => $id.'_', - &> - <SCRIPT TYPE="text/javascript"> - document.getElementById("<%$id%>_<%$field%>").onchange = function() { - if (this.value == "P" || this.value == "E") { changepw<%$id%>_toggle(true); } - return false - } +% #password form +% } elsif ( $field eq 'password') { + <INPUT TYPE = "text" + NAME = "<%$name%>_<%$field%>" + ID = "changepw<%$id%>_<%$field%>" + SIZE = "<% $size{$field} || 14 %>" + VALUE = "" + placeholder = "<% $value |h %>" + > + <SCRIPT> + <% $js %> </SCRIPT> % } elsif ( $field eq 'invoice_dest' ) { % my $curr_value = $cgi->param($name . '_' . $field); @@ -96,6 +97,9 @@ % } <BR> <FONT SIZE="-1"><% $label{$field} %></FONT> +% if ( $field eq 'password' ) { + <DIV ID="changepw<%$id%>_<%$field%>_result" STYLE="font-size: smaller"></DIV> +% } </TD> % } </TR> @@ -114,6 +118,7 @@ my $name = $opt{'element_name'} || $opt{'field'} || 'contactnum'; my $id = $opt{'id'} || 'contactnum'; my $curr_value = $opt{'curr_value'} || $opt{'value'}; +my $contactnum = $curr_value ? $curr_value : '0'; my $onchange = ''; if ( $opt{'onchange'} ) { @@ -143,6 +148,11 @@ tie my %label, 'Tie::IxHash', 'selfservice_access' => 'Self-service' ; +unless ($opt{'for_prospect'}) { + $label{'selfservice_access'} = 'Self-service'; + $label{'password'} = 'Password'; +} + my $first = 0; foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) { next if $phone_type->typename =~ /^(Home|Fax)$/; @@ -155,7 +165,19 @@ $label{'comment'} = 'Comment'; my @fields = $opt{'name_only'} ? qw( first last ) : keys %label; -my $pwd_change_label = 'Change Password'; -$pwd_change_label = 'Setup Password' unless $contact->_password; +my $js = qq( + add_password_validation('changepw$id\_password', 'submit', '', '$contactnum'); + + var selfService = document.getElementById("$id\_selfservice_access").value; + + if (selfService !== "Y") { document.getElementById("changepw$id\_password").disabled = 'true'; } + document.getElementById("$id\_selfservice_access").onchange = function() { + if (this.value == "P" || this.value == "E" || this.value =="Y") { + document.getElementById("changepw$id\_password").disabled = ''; + } + else { document.getElementById("changepw$id\_password").disabled = 'true'; } + return false; + } +); </%init> diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css index 8ad724a4f..1e5d1e87f 100644 --- a/httemplate/elements/freeside.css +++ b/httemplate/elements/freeside.css @@ -291,6 +291,10 @@ tr.row1 { font-style: italic; } +th, td { + vertical-align: top; +} + /* border at the top of the footer, but not between footer rows */ .grid tfoot tr:first-child td { border-top: 1px dashed black; diff --git a/httemplate/elements/polygon.html b/httemplate/elements/polygon.html index 813aa419f..b5aa93753 100644 --- a/httemplate/elements/polygon.html +++ b/httemplate/elements/polygon.html @@ -5,13 +5,16 @@ my $id = $opt{'id'} || $opt{'field'}; my $div_id = "div_$id"; my $vertices_json = $opt{'curr_value'} || '[]'; + +my $apikey = FS::Conf->new->config('google_maps_api_key'); + </%init> <& hidden.html, %opt &> <div id="<% $div_id %>" style="height: 600px; width: 600px"></div> <div id="<% $div_id %>_hint" style="width: 100%; border: 2px solid black; text-align: center; box-sizing: border-box; padding: 4px"> </div> <script src="<% $fsurl %>elements/jquery.js"></script> -<script src="https://maps.googleapis.com/maps/api/js?libraries=drawing&v=3.22"></script> +<script src="https://maps.googleapis.com/maps/api/js?libraries=drawing&v=3.22<% $apikey ? "&key=$apikey" : '' %>"></script> <script> var map; var drawingManager; diff --git a/httemplate/elements/select-country.html b/httemplate/elements/select-country.html index c98147907..286826752 100644 --- a/httemplate/elements/select-country.html +++ b/httemplate/elements/select-country.html @@ -91,15 +91,13 @@ Example: > % unless ( $opt{'disable_empty'} ) { - <OPTION VALUE=""><% $opt{'empty_label'} || '(all)' %> + <OPTION VALUE=""><% $opt{'empty_label'} || '(all)' %></OPTION> % } % foreach my $country ( @all_countries ) { - - <OPTION VALUE="<% $country |h %>" - <% $country eq $opt{'country'} ? ' SELECTED' : '' %> - ><% FS::geocode_Mixin->code2country($country). " ($country)" %> - + <OPTION VALUE="<% $country |h %>"<% $country eq $opt{'country'} ? ' SELECTED' : '' %>> + <% FS::geocode_Mixin->code2country($country). " ($country)" |h %> + </OPTION> % } </SELECT> diff --git a/httemplate/elements/select-month_year.html b/httemplate/elements/select-month_year.html index ad84b915d..9091bae5d 100644 --- a/httemplate/elements/select-month_year.html +++ b/httemplate/elements/select-month_year.html @@ -3,16 +3,15 @@ <% $empty ? '<OPTION VALUE="">' : '' %> % foreach ( 1 .. 12 ) { - <OPTION<% $_ == $mon ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $mon[$_-1] %> + <OPTION<% $_ == $mon ? ' SELECTED' : '' %> VALUE="<% sprintf('%02d', $_) %>"><% $mon[$_-1] %></OPTION> % } - </SELECT>/<SELECT NAME="<% $prefix %>_year" SIZE="1" <% $disabled%>> <% $empty ? '<OPTION VALUE="">' : '' %> % for ( $start_year .. $end_year ) { - <OPTION<% $_ == $year ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $_ %> + <OPTION<% $_ == $year ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $_ %></OPTION> % } </SELECT> diff --git a/httemplate/elements/select-state.html b/httemplate/elements/select-state.html index 115a98d98..7606e2527 100644 --- a/httemplate/elements/select-state.html +++ b/httemplate/elements/select-state.html @@ -27,16 +27,13 @@ Example: > % unless ( $opt{'disable_empty'} ) { - <OPTION VALUE=""<% $opt{state} eq '' ? ' SELECTED' : '' %>><% $opt{empty_label} %> + <OPTION VALUE=""<% $opt{state} eq '' ? ' SELECTED' : '' %>><% $opt{empty_label} %></OPTION> % } % foreach my $state ( keys %states ) { - - <OPTION VALUE="<% $state |h %>"<% $state eq $opt{'state'} ? ' SELECTED' : '' %>><% $states{$state} || '(n/a)' |h %> - + <OPTION VALUE="<% $state |h %>"<% $state eq $opt{'state'} ? ' SELECTED' : '' %>><% $states{$state} || '(n/a)' |h %></OPTION> % } - </SELECT> <%init> diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html index 3f6c8805d..4ac0887fd 100644 --- a/httemplate/elements/select-table.html +++ b/httemplate/elements/select-table.html @@ -83,11 +83,11 @@ Example: % || ( $value eq $pre_opt ); <OPTION VALUE="<% $pre_opt %>" <% $selected ? 'SELECTED' : '' %> - ><% $pre_label %> + ><% $pre_label %></OPTION> % } % unless ( $opt{'multiple'} || $opt{'disable_empty'} ) { - <OPTION VALUE=""><% $opt{'empty_label'} || 'all' %> + <OPTION VALUE=""><% $opt{'empty_label'} || 'all' %></OPTION> % } % # XXX fix this eventually, when we have time to test it @@ -117,7 +117,7 @@ Example: ? &{ $opt{'label_callback'} }( $record ) : $record->$name_col() |h - %> + %></OPTION> % } % while ( @post_options ) { @@ -127,7 +127,7 @@ Example: % || ( $value eq $post_opt ); <OPTION VALUE="<% $post_opt %>" <% $selected ? 'SELECTED' : '' %> - ><% $post_label %> + ><% $post_label %></OPTION> % } </SELECT> diff --git a/httemplate/elements/validate_password.html b/httemplate/elements/validate_password.html index a8c06774e..10471d974 100644 --- a/httemplate/elements/validate_password.html +++ b/httemplate/elements/validate_password.html @@ -15,58 +15,10 @@ should be the input id plus '_result'. </%doc> -<& '/elements/xmlhttp.html', - 'url' => $p.'misc/xmlhttp-validate_password.html', - 'subs' => [ 'validate_password' ], - 'method' => 'POST', # important not to put passwords in url -&> -<SCRIPT> -function add_password_validation (fieldid, submitid) { - var inputfield = document.getElementById(fieldid); - inputfield.onkeydown = function(e) { - var key; - if (window.event) { key = window.event.keyCode; } - else { key = e.which; } // for ff browsers - // some browsers allow the enter key to submit a form even if the submit button is disabled - // below prevents enter key from submiting form if password has not been validated. - if (key == '13') { - var check = checkPasswordValidation(); - return check; - } - } - inputfield.onkeyup = function () { - var fieldid = this.id+'_result'; - var resultfield = document.getElementById(fieldid); - if (this.value) { - resultfield.innerHTML = '<SPAN STYLE="color: blue;">Validating password...</SPAN>'; - validate_password('fieldid',fieldid,'svcnum','<% $opt{'svcnum'} %>','pkgnum','<% $opt{'pkgnum'} %>','contactnum','<% $opt{'contactnum'} %>','password',this.value, - function (result) { - result = JSON.parse(result); - var resultfield = document.getElementById(result.fieldid); - if (resultfield) { - var errorimg = '<IMG SRC="<% $p %>images/error.png" style="width: 1em; display: inline-block; padding-right: .5em">'; - var validimg = '<IMG SRC="<% $p %>images/tick.png" style="width: 1em; display: inline-block; padding-right: .5em">'; - if (result.valid) { - resultfield.innerHTML = validimg+'<SPAN STYLE="color: green;">Password valid!</SPAN>'; - if (submitid){ document.getElementById(submitid).disabled = false; } - } else if (result.error) { - resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.error+'</SPAN>'; - if (submitid){ document.getElementById(submitid).disabled = true; } - } else { - result.syserror = result.syserror || 'Server error'; - resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>'; - if (submitid){ document.getElementById(submitid).disabled = true; } - } - } - } - ); - } else { - resultfield.innerHTML = ''; - } - }; -} +<& '/elements/validate_password_js.html', %opt &> -add_password_validation('<% $opt{'fieldid'} %>', '<% $opt{'submitid'} %>'); +<SCRIPT> + add_password_validation('<% $opt{'fieldid'} %>', '<% $opt{'submitid'} %>', '<% $opt{'svcnum'} %>', '<% $opt{'contactnum'} %>'); </SCRIPT> <%init> diff --git a/httemplate/elements/validate_password_js.html b/httemplate/elements/validate_password_js.html new file mode 100644 index 000000000..64db0a97b --- /dev/null +++ b/httemplate/elements/validate_password_js.html @@ -0,0 +1,71 @@ +<%doc> + +JavaScript to perform password validation + + <& '/elements/validate_password_js.html', + contactnum => $contactnum, + svcnum => $svcnum + &> + +The ID of the input field can be anything; the ID of the DIV in which to display results +should be the input id plus '_result'. + +</%doc> + +<& '/elements/xmlhttp.html', + 'url' => $p.'misc/xmlhttp-validate_password.html', + 'subs' => [ 'validate_password' ], + 'method' => 'POST', # important not to put passwords in url +&> +<SCRIPT> +function add_password_validation (fieldid, submitid, svcnum, contactnum) { + var inputfield = document.getElementById(fieldid); + inputfield.onkeydown = function(e) { + var key; + if (window.event) { key = window.event.keyCode; } + else { key = e.which; } // for ff browsers + // some browsers allow the enter key to submit a form even if the submit button is disabled + // below prevents enter key from submiting form if password has not been validated. + if (key == '13') { + var check = checkPasswordValidation(fieldid); + return check; + } + } + inputfield.onkeyup = function () { + var fieldid = this.id+'_result'; + var resultfield = document.getElementById(fieldid); + if (this.value) { + resultfield.innerHTML = '<SPAN STYLE="color: blue;">Validating password...</SPAN>'; + validate_password('fieldid',fieldid,'svcnum','<% $opt{'svcnum'} %>','contactnum', contactnum,'password',this.value, + function (result) { + result = JSON.parse(result); + var resultfield = document.getElementById(result.fieldid); + if (resultfield) { + var errorimg = '<IMG SRC="<% $p %>images/error.png" style="width: 1em; display: inline-block; padding-right: .5em">'; + var validimg = '<IMG SRC="<% $p %>images/tick.png" style="width: 1em; display: inline-block; padding-right: .5em">'; + if (result.valid) { + resultfield.innerHTML = validimg+'<SPAN STYLE="color: green;">Password valid!</SPAN>'; + if (submitid){ document.getElementById(submitid).disabled = false; } + } else if (result.error) { + resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.error+'</SPAN>'; + if (submitid){ document.getElementById(submitid).disabled = true; } + } else { + result.syserror = result.syserror || 'Server error'; + resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>'; + if (submitid){ document.getElementById(submitid).disabled = true; } + } + } + } + ); + } else { + resultfield.innerHTML = ''; + if (submitid){ document.getElementById(submitid).disabled = false; } + } + }; +} + +</SCRIPT> + +<%init> +my %opt = @_; +</%init>
\ No newline at end of file diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index 44904fba6..9e530cf0a 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -59,9 +59,8 @@ <TH><% mt('Exp.') |h %></TH> <TD> <SELECT NAME="month"> -% for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) { - - <OPTION<% $_ == $month ? ' SELECTED' : '' %>><% $_ %> +% for ( map{ sprintf('%02d',$_) } (1..12) ) { + <OPTION VALUE="<% $_ %>"<% $_ == $month ? ' SELECTED' : '' %>><% $_ %></OPTION> % } </SELECT> @@ -70,8 +69,7 @@ <TD> <SELECT NAME="year"> % my @a = localtime; for ( $a[5]+1900 .. $a[5]+1915 ) { - - <OPTION<% $_ == $year ? ' SELECTED' : '' %>><% $_ %> + <OPTION VALUE="<% $_ %>"<% $_ == $year ? ' SELECTED' : '' %>><% $_ %></OPTION> % } </SELECT> diff --git a/httemplate/misc/process/change-password.html b/httemplate/misc/process/change-password.html index a3e060168..37ad6d915 100644 --- a/httemplate/misc/process/change-password.html +++ b/httemplate/misc/process/change-password.html @@ -18,7 +18,7 @@ <% $cgi->redirect($fsurl.'view/svc_acct.cgi?'.$cgi->query_string) %> % } % elsif ($contactnum) { - <% $cgi->redirect($fsurl.'edit/cust_main-contacts.html?'.$cgi->param('custnum')) %> + <% $cgi->redirect($fsurl.'view/cust_main.cgi?'.$cgi->param('custnum')) %> % } % } @@ -34,6 +34,10 @@ my $curuser = $FS::CurrentUser::CurrentUser; $cgi->param('svcnum') =~ /^(\d+)$/ or die "illegal svcnum" if $cgi->param('svcnum'); my $svcnum = $1; +foreach my $prefix (grep /^(.*)(password)$/, $cgi->param) { + $cgi->param('password' => $cgi->param($prefix)); +} + $cgi->param('contactnum') =~ /^(\d+)$/ or die "illegal contactnum" if $cgi->param('contactnum'); my $contactnum = $1; diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi index 9458217c1..5f945a71a 100644 --- a/httemplate/misc/process/payment.cgi +++ b/httemplate/misc/process/payment.cgi @@ -42,11 +42,11 @@ if ( $cgi->param('fee') =~ /^\s*(\d*(\.\d\d)?)\s*$/ ) { $amount = sprintf('%.2f', $amount + $fee); } -$cgi->param('year') =~ /^(\d+)$/ +$cgi->param('year') =~ /^(\d{4})/ or errorpage("illegal year ". $cgi->param('year')); my $year = $1; -$cgi->param('month') =~ /^(\d+)$/ +$cgi->param('month') =~ /^(\d{2})/ or errorpage("illegal month ". $cgi->param('month')); my $month = $1; diff --git a/httemplate/misc/xmlhttp-validate_password.html b/httemplate/misc/xmlhttp-validate_password.html index b340170f8..95f1fd33c 100644 --- a/httemplate/misc/xmlhttp-validate_password.html +++ b/httemplate/misc/xmlhttp-validate_password.html @@ -29,14 +29,14 @@ my $validate_password = sub { $result{'syserror'} = 'Invoked without password' unless $password; return \%result if $result{'syserror'}; - if ($arg{'contactnum'}) { + if ($arg{'contactnum'} =~ /^\d+$/) { my $contactnum = $arg{'contactnum'}; $result{'syserror'} = 'Invalid contactnum' unless $contactnum =~ /^\d*$/; return \%result if $result{'syserror'}; my $contact = $contactnum ? qsearchs('contact',{'contactnum' => $contactnum}) - : ''; + : (new FS::contact {}); $result{'error'} = $contact->is_password_allowed($password); } diff --git a/httemplate/search/cust_timespan.html b/httemplate/search/cust_timespan.html index 13fc6a1ab..254891cec 100644 --- a/httemplate/search/cust_timespan.html +++ b/httemplate/search/cust_timespan.html @@ -88,7 +88,8 @@ my $active_date = 'select min(setup) from cust_pkg left join part_pkg using (pkg ## set cancel date range here my($beginning_date, $ending_date) = FS::UI::Web::parse_beginning_ending($cgi, ''); -my $cancel_date = 'select max(cancel) from cust_pkg left join part_pkg using (pkgpart) where cust_pkg.custnum = cust_main.custnum and part_pkg.freq > \'0\' and (cancel >= '.$beginning_date.' and cancel <= '.$ending_date.')'; +my $max_cancel_sql = "select max(cancel) from cust_pkg left join part_pkg using (pkgpart) where cust_pkg.custnum = cust_main.custnum and part_pkg.freq > \'0\'"; +my $cancel_date = $max_cancel_sql.' and (('.$max_cancel_sql.') >= '.$beginning_date.' and ('.$max_cancel_sql.') <= '.$ending_date.')'; my $cancel_reason = 'select reason.reason from cust_pkg left join cust_pkg_reason on (cust_pkg.pkgnum = cust_pkg_reason.pkgnum) @@ -101,8 +102,6 @@ my @fields = ( 'custnum', 'custname', $location_sub, 'daytime', $email_sub, 'act my @links = ( $customer_link, $customer_link, '', '', '', '', '', '', '' ); my @select = ( 'cust_main.*', - 'cust_location.*', - 'part_pkg.*', "(select to_char((select to_timestamp((".$active_date."))), 'Mon DD YYYY')) AS active_date", "(select to_char((select to_timestamp((".$cancel_date."))), 'Mon DD YYYY')) AS cancel_date", "($cancel_reason) AS cancel_reason", diff --git a/httemplate/search/elements/gmap.html b/httemplate/search/elements/gmap.html index 47db1507d..1caf76718 100644 --- a/httemplate/search/elements/gmap.html +++ b/httemplate/search/elements/gmap.html @@ -37,6 +37,9 @@ Generic Google Maps front end. </%doc> <%init> + +my $apikey = FS::Conf->new->config('google_maps_api_key'); + foreach (@features) { $_->{type} = 'Feature'; # any other per-feature massaging can go here @@ -57,7 +60,7 @@ body { height: 100%; margin: 0px; padding: 0px } #map_canvas { height: 100%; } </style> -<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3"> +<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3<% $apikey ? "&key=$apikey" : '' %>"> </script> <script type="text/javascript"> diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html index f73483ae1..19118ea27 100644 --- a/httemplate/view/cust_main/contacts_new.html +++ b/httemplate/view/cust_main/contacts_new.html @@ -6,6 +6,7 @@ % my $bgcolor1 = '#eeeeee'; % my $bgcolor2 = '#ffffff'; % my $bgcolor = $bgcolor2; +% my $count = 0; <TR> <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Type</TH> <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Contact</TH> @@ -29,6 +30,16 @@ Enabled %# <FONT SIZE="-1"><A HREF="XXX">disable</A> %# <A HREF="XXX">re-email</A></FONT> + <FONT SIZE="-1"> + <& /elements/change_password.html, + 'contact_num' => $contact->contactnum, + 'custnum' => $contact->custnum, + 'no_label_display' => '', + 'label' => 'change password', + 'curr_value' => '', + 'pre_pwd_field_label' => 'contact'.$count.'_', + &> + </FONT> % } else { Disabled %# <FONT SIZE="-1"><A HREF="XXX">enable</A></FONT> @@ -51,6 +62,7 @@ % } else { % $bgcolor = $bgcolor1; % } +% $count++; % } </TABLE> <%once> @@ -64,5 +76,6 @@ my( $cust_main ) = @_; #my $conf = new FS::Conf; my @contacts = $cust_main->cust_contact; +my $display = scalar(@contacts) > 0; </%init> diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index 0517c307a..189fe5e6f 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -33,6 +33,9 @@ my @fields = ( { field => 'routernum', value_callback => \&router }, 'speed_down', 'speed_up', + 'speed_test_down', + 'speed_test_up', + 'speed_test_latency', { field => 'ip_addr', value_callback => \&ip_addr }, { field => 'sectornum', value_callback => \§ornum }, { field => 'mac_addr', type=>'mac_addr', value_callback => \&mac_addr }, |
