summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2018-09-13 10:54:53 -0700
committerIvan Kohler <ivan@freeside.biz>2018-09-13 10:54:53 -0700
commitd7b4beaeb975c55f5203aef275c7c01853fa51f2 (patch)
tree2eb0a708bfbfdbfaa3c6db935911a8fd830446d4
parent2c0ab6236b187a39fbb6e2046180e2531a391333 (diff)
parent4cf578816f5a491b1f66ddb5a9e832457ebe1332 (diff)
Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE_3_BRANCH
-rw-r--r--FS/FS/Record.pm54
-rw-r--r--FS/FS/Report/Table.pm2
-rw-r--r--FS/FS/contact.pm12
-rw-r--r--FS/FS/cust_main.pm82
-rw-r--r--FS/FS/deploy_zone.pm2
-rw-r--r--FS/FS/part_export/saisei.pm60
-rwxr-xr-xFS/FS/svc_broadband.pm6
-rwxr-xr-xbin/move_svc_broadband_speeds.pl66
-rw-r--r--httemplate/edit/cust_main-contacts.html43
-rw-r--r--httemplate/edit/elements/edit.html2
-rw-r--r--httemplate/edit/process/cust_main-contacts.html2
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi2
-rw-r--r--httemplate/elements/change_password.html15
-rw-r--r--httemplate/elements/city.html4
-rw-r--r--httemplate/elements/contact.html58
-rw-r--r--httemplate/elements/freeside.css4
-rw-r--r--httemplate/elements/polygon.html5
-rw-r--r--httemplate/elements/select-country.html10
-rw-r--r--httemplate/elements/select-month_year.html5
-rw-r--r--httemplate/elements/select-state.html7
-rw-r--r--httemplate/elements/select-table.html8
-rw-r--r--httemplate/elements/validate_password.html54
-rw-r--r--httemplate/elements/validate_password_js.html71
-rw-r--r--httemplate/misc/payment.cgi8
-rw-r--r--httemplate/misc/process/change-password.html6
-rw-r--r--httemplate/misc/process/payment.cgi4
-rw-r--r--httemplate/misc/xmlhttp-validate_password.html4
-rw-r--r--httemplate/search/cust_timespan.html5
-rw-r--r--httemplate/search/elements/gmap.html5
-rw-r--r--httemplate/view/cust_main/contacts_new.html13
-rw-r--r--httemplate/view/svc_broadband.cgi3
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' => '&nbsp;',
- },
- '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' => '&nbsp;',
+ },
+ '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">&nbsp;</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 => \&sectornum },
{ field => 'mac_addr', type=>'mac_addr', value_callback => \&mac_addr },