From: Ivan Kohler Date: Thu, 15 Mar 2012 20:56:48 +0000 (-0700) Subject: Merge branch 'master' of git.freeside.biz:/home/git/freeside X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=7d68066ea33f9f85fe14ce663372642d7ec2ad20;hp=d622dc369cc0856fb791658b35f889470a7da605 Merge branch 'master' of git.freeside.biz:/home/git/freeside --- diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 7d177f9fe..acd0c6e85 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -2485,7 +2485,7 @@ sub myaccount_passwd { unless $svc_acct->check_password($p->{'old_password'}); } - $svc_acct->_password($p->{'new_password'}); + $svc_acct->set_password($p->{'new_password'}); my $error = $svc_acct->replace(); my($label, $value) = $svc_acct->cust_svc->label; @@ -2626,7 +2626,7 @@ sub process_reset_passwd { my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) or return { 'error' => "Service not found" }; - $svc_acct->_password($p->{'new_password'}); + $svc_acct->set_password($p->{'new_password'}); my $error = $svc_acct->replace(); my($label, $value) = $svc_acct->cust_svc->label; diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 1112f52d7..a36d2dcdb 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2484,7 +2484,7 @@ sub tables_hashref { 'routername', 'varchar', '', $char_d, '', '', 'svcnum', 'int', 'NULL', '', '', '', 'agentnum', 'int', 'NULL', '', '', '', - 'auto_addr', 'char', 'NULL', 1, '', '', + 'manual_addr', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'routernum', 'unique' => [], diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index 1769fe9c1..3a6b01ba5 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -152,7 +152,7 @@ following fields are currently supported: =item svcnum - Link to customer service (see L) -=item freesidestatus - NULL, processing-tiered, rated, done +=item freesidestatus - NULL, processing-tiered, rated, done, skipped, no-charge, failed =item freesiderewritestatus - NULL, done, skipped @@ -545,7 +545,7 @@ sub rate_prefix { ); if ( $reason ) { warn "not charging for CDR ($reason)\n" if $DEBUG; - return $self->set_status_and_rated_price( 'rated', + return $self->set_status_and_rated_price( 'skipped', 0, $opt{'svcnum'}, ); diff --git a/FS/FS/detail_format.pm b/FS/FS/detail_format.pm index f70acc663..88cc02f83 100644 --- a/FS/FS/detail_format.pm +++ b/FS/FS/detail_format.pm @@ -235,6 +235,7 @@ sub duration { my $cdr = shift; my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr; my $sec = $object->rated_seconds if $object; + $sec ||= 0; # XXX termination objects don't have rated_granularity so this may # result in inbound CDRs being displayed as min/sec when they shouldn't. # Should probably fix this. diff --git a/FS/FS/part_event.pm b/FS/FS/part_event.pm index 31d2afd23..62f16fa1c 100644 --- a/FS/FS/part_event.pm +++ b/FS/FS/part_event.pm @@ -253,7 +253,7 @@ sub templatename { } } -=item targets +=item targets OPTIONS Returns all objects (of type C, for this object's C) eligible for processing under this event, as of right now. @@ -268,7 +268,8 @@ but can be useful when configuring events. sub targets { my $self = shift; - my $time = time; # $opt{'time'}? + my %opt = @_; + my $time = $opt{'time'} || time; my $eventpart = $self->eventpart; $eventpart =~ /^\d+$/ or die "bad eventpart $eventpart"; @@ -305,8 +306,8 @@ sub targets { }); my @tested_objects; foreach my $object ( @objects ) { - my $cust_event = $self->new_cust_event($object, 'time' => $time); - next unless $cust_event->test_conditions; + my $cust_event = $self->new_cust_event($object); + next unless $cust_event->test_conditions('time' => $time); $object->set('cust_event', $cust_event); push @tested_objects, $object; diff --git a/FS/FS/part_event/Condition/signupdate_day.pm b/FS/FS/part_event/Condition/signupdate_day.pm new file mode 100644 index 000000000..dbe9e60a1 --- /dev/null +++ b/FS/FS/part_event/Condition/signupdate_day.pm @@ -0,0 +1,54 @@ +package FS::part_event::Condition::signupdate_day; + +use strict; +use Tie::IxHash; + +use base qw( FS::part_event::Condition ); + +sub description { + "Customer signed up on the same day of month as today"; +} + +sub option_fields { + ( + 'delay' => { label => 'Delay additional days', + type => 'text', + value => '0', + }, + ); +} + +sub condition { + my( $self, $object, %opt ) = @_; + + my $cust_main = $self->cust_main($object); + + my ($today) = (localtime($opt{'time'}))[3]; + + my $delay = $self->option('delay') || 0; + my $signupday = ((localtime($cust_main->signupdate + $delay * 86400))[3] - 1) + % 28 + 1; + + $today == $signupday; +} + +sub condition_sql { + my( $class, $table, %opt ) = @_; + my $mday; + if ( $opt{'driver_name'} eq 'Pg' ) { + $mday = sub{ "EXTRACT( DAY FROM TO_TIMESTAMP($_[0]) )::INTEGER" }; + } + elsif ( $opt{'driver_name'} eq 'mysql' ) { + $mday = sub{ "DAY( FROM_UNIXTIME($_[0]) )" }; + } + else { + return 'true'; + } + + my $delay = $class->condition_sql_option_integer('delay', + $opt{'driver_name'}); # returns 0 for null + $mday->($opt{'time'}) . ' = '. + '(' . $mday->("cust_main.signupdate + $delay * 86400") . ' - 1) % 28 + 1'; +} + +1; diff --git a/FS/FS/router.pm b/FS/FS/router.pm index 99373e5d1..6fa44b408 100755 --- a/FS/FS/router.pm +++ b/FS/FS/router.pm @@ -40,8 +40,9 @@ fields are currently supported: =item svcnum - svcnum of the owning FS::svc_broadband, if appropriate -=item auto_addr - flag to automatically assign IP addresses to services -linked to this router ('Y' or null). +=item manual_addr - set to 'Y' to allow services linked to this router +to have any IP address, rather than one in an address block belonging +to the router. =back @@ -86,7 +87,7 @@ sub check { my $error = $self->ut_numbern('routernum') || $self->ut_text('routername') - || $self->ut_enum('auto_addr', [ '', 'Y' ]) + || $self->ut_enum('manual_addr', [ '', 'Y' ]) || $self->ut_agentnum_acl('agentnum', 'Broadband global configuration') ; return $error if $error; @@ -146,7 +147,7 @@ sub addr_block { sub auto_addr_block { my $self = shift; - return () if !$self->auto_addr; + return () if $self->manual_addr; return qsearch('addr_block', { routernum => $self->routernum, manual_flag => '' }); } diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 139f92715..e67db43c6 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -2524,7 +2524,8 @@ sub check_password { if ( $self->_password_encoding eq 'ldap' ) { - my $auth = from_rfc2307 Authen::Passphrase $self->_password; + $password =~ s/^{PLAIN}/{CLEARTEXT}/; + my $auth = from_rfc2307 Authen::Passphrase $password; return $auth->match($check_password); } elsif ( $self->_password_encoding eq 'crypt' ) { diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 109620011..212a4bf24 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -135,7 +135,7 @@ sub table_info { sub table { 'svc_broadband'; } -sub table_dupcheck_fields { ( 'mac_addr' ); } +sub table_dupcheck_fields { ( 'ip_addr', 'mac_addr' ); } =item search HASHREF @@ -406,7 +406,13 @@ sub check { } my $agentnum = $cust_pkg->cust_main->agentnum if $cust_pkg; - if ($self->routernum) { + if ( $conf->exists('auto_router') and $self->ip_addr and !$self->routernum ) { + # assign_router is guaranteed to provide a router that's legal + # for this agent and svcpart + my $error = $self->_check_ip_addr || $self->assign_router; + return $error if $error; + } + elsif ($self->routernum) { return "Router ".$self->routernum." does not provide this service" unless qsearchs('part_svc_router', { svcpart => $svcpart, @@ -417,16 +423,19 @@ sub check { return "Router ".$self->routernum." does not serve this customer" if $router->agentnum and $router->agentnum != $agentnum; - if ( $router->auto_addr ) { + if ( $router->manual_addr ) { + $self->blocknum(''); + } + else { my $addr_block = $self->addr_block; unless ( $addr_block and $addr_block->manual_flag ) { my $error = $self->assign_ip_addr; return $error if $error; } } - else { - $self->blocknum(''); - } + + my $error = $self->_check_ip_addr; + return $error if $error; } # if $self->routernum if ( $cust_pkg && ! $self->latitude && ! $self->longitude ) { @@ -440,15 +449,12 @@ sub check { } } - $error = $self->_check_ip_addr; - return $error if $error; - $self->SUPER::check; } =item assign_ip_addr -Assign an address block matching the selected router, and the selected block +Assign an IP address matching the selected router, and the selected block if there is one. =cut @@ -469,6 +475,7 @@ sub assign_ip_addr { else { return ''; } +#warn "assigning ip address in blocks\n".join("\n",map{$_->cidr} @blocks)."\n"; foreach my $block ( @blocks ) { if ( $self->ip_addr and $block->NetAddr->contains($self->NetAddr) ) { @@ -487,6 +494,29 @@ sub assign_ip_addr { } } +=item assign_router + +Assign an address block and router matching the selected IP address. +Does nothing if IP address is null. + +=cut + +sub assign_router { + my $self = shift; + return '' if !$self->ip_addr; + #warn "assigning router/block for ".$self->ip_addr."\n"; + foreach my $router ($self->allowed_routers) { + foreach my $block ($router->addr_block) { + if ( $block->NetAddr->contains($self->NetAddr) ) { + $self->blocknum($block->blocknum); + $self->routernum($block->routernum); + return ''; + } + } + } + return $self->ip_addr.' is not in an allowed block.'; +} + sub _check_ip_addr { my $self = shift; @@ -494,6 +524,9 @@ sub _check_ip_addr { return '' if $conf->exists('svc_broadband-allow_null_ip_addr'); return 'IP address required'; } + else { + return 'Cannot parse address: '.$self->ip_addr unless $self->NetAddr; + } # if (my $dup = qsearchs('svc_broadband', { # ip_addr => $self->ip_addr, # svcnum => {op=>'!=', value => $self->svcnum} @@ -506,10 +539,17 @@ sub _check_ip_addr { sub _check_duplicate { my $self = shift; - return "MAC already in use" - if ( $self->mac_addr && - scalar( qsearch( 'svc_broadband', { 'mac_addr', $self->mac_addr } ) ) - ); + $self->lock_table; + + my @dup; + @dup = $self->find_duplicates('global', 'ip_addr'); + if ( @dup ) { + return "IP address in use (svcnum ".$dup[0]->svcnum.")"; + } + @dup = $self->find_duplicates('global', 'mac_addr'); + if ( @dup ) { + return "MAC address in use (svcnum ".$dup[0]->svcnum.")"; + } ''; } @@ -558,8 +598,15 @@ Returns a list of allowed FS::router objects. sub allowed_routers { my $self = shift; my $svcpart = $self->svcnum ? $self->cust_svc->svcpart : $self->svcpart; - map { $_->router } qsearch('part_svc_router', + my @r = map { $_->router } qsearch('part_svc_router', { svcpart => $self->cust_svc->svcpart }); + if ( $self->cust_main ) { + my $agentnum = $self->cust_main->agentnum; + return grep { !$_->agentnum or $_->agentnum == $agentnum } @r; + } + else { + return @r; + } } =back diff --git a/FS/FS/svc_hardware.pm b/FS/FS/svc_hardware.pm index b4eb8ccfa..22e627538 100644 --- a/FS/FS/svc_hardware.pm +++ b/FS/FS/svc_hardware.pm @@ -164,7 +164,7 @@ sub check { if ( $conf->exists('svc_hardware-check_mac_addr') ) { $hw_addr = uc($hw_addr); $hw_addr =~ /^[0-9A-F]{12}$/ - or return "Illegal (MAC address) ".$self->getfield('hw_addr'); + or return "Illegal (MAC address) '".$self->getfield('hw_addr')."'"; } $self->setfield('hw_addr', $hw_addr); diff --git a/httemplate/browse/part_event.html b/httemplate/browse/part_event.html index 6be28602d..03996435e 100644 --- a/httemplate/browse/part_event.html +++ b/httemplate/browse/part_event.html @@ -45,7 +45,8 @@ my $link = [ $p.'edit/part_event.html?', 'eventpart' ]; my $event_sub = sub { my $part_event = shift; my $onclick = include('/elements/popup_link_onclick.html', - action => $p.'view/part_event-targets.html?'.$part_event->eventpart, + action => $p.'view/part_event-targets.html?eventpart='. + $part_event->eventpart, actionlabel => 'Event query - '.$part_event->event, width => 650, height => 420, diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi index 21047d7fc..ef8ad3160 100644 --- a/httemplate/browse/router.cgi +++ b/httemplate/browse/router.cgi @@ -17,7 +17,7 @@ shift->addr_block ); }, - sub { shift->auto_addr ? 'Automatic' : 'Manual' }, + sub { shift->manual_addr ? 'Manual' : 'Automatic' }, sub { 'Delete' }, ], 'links' => [ [ "${p2}edit/router.cgi?", 'routernum' ], diff --git a/httemplate/docs/credits.html b/httemplate/docs/credits.html index 7b3b0b93a..9bb1decea 100644 --- a/httemplate/docs/credits.html +++ b/httemplate/docs/credits.html @@ -61,6 +61,7 @@ Joe Camadine
Chris Cappuccio
Rebecca Cardennis
Shane Chrisp
+Kendall Conrad
Luke Crawford
Brad Dameron
Dave Denney
diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html index e0e40b76e..fab8cd09f 100644 --- a/httemplate/docs/license.html +++ b/httemplate/docs/license.html @@ -112,6 +112,11 @@ terms as Perl (GPL/Artistic). Contains code derived from HTML::GoogleMapsV3 by David Peters, licensed under the same terms as Perl (GPL/Artistic). +

+Contains the Masked Input JavaScript library by Kendall Conrad, licensed under +a Creative Commons +Attribution-ShareAlike 3.0 United States license. +

diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi index 4184f5fa6..31def255c 100644 --- a/httemplate/edit/process/svc_broadband.cgi +++ b/httemplate/edit/process/svc_broadband.cgi @@ -13,6 +13,9 @@ die "access denied" sub precheck { my $cgi = shift; + if ( !defined($cgi->param('ip_addr')) ) { + $cgi->param('ip_addr', $cgi->param('prev_ip_addr') || ''); + } $cgi->param("usergroup", [ $cgi->param('usergroup') ]); '' } diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi index 6672d5d75..fdcd7b3b3 100755 --- a/httemplate/edit/router.cgi +++ b/httemplate/edit/router.cgi @@ -7,13 +7,13 @@ 'routername' => 'Name', 'svc_part' => 'Service', 'agentnum' => 'Agent', - 'auto_addr' => 'Assign IP addresses automatically', + 'manual_addr' => 'Assign IP addresses manually', }, 'fields' => [ { 'field'=>'routername', 'type'=>'text', 'size'=>32 }, { 'field'=>'agentnum', 'type'=>'select-agent' }, { 'field'=>'svcnum', 'type'=>'hidden' }, - { 'field'=>'auto_addr','type'=>'checkbox','value'=>'Y'}, + { 'field'=>'manual_addr','type'=>'checkbox','value'=>'Y'}, ], 'error_callback' => $callback, 'edit_callback' => $callback, diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index 8fccb1fa8..b266928a1 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -102,9 +102,10 @@ END my @fields = ( qw( description speed_down speed_up ), { field=>'sectornum', type=>'select-tower_sector', }, - { field=>'routernum', type=>'select-router_block_ip', }, - qw( mac_addr latitude longitude altitude vlan_profile - performance_profile authkey plan_id ), + { field=>'routernum', type=>'select-router_block_ip' }, + { field=>'mac_addr' , type=>'input-mac_addr' }, + qw( latitude longitude altitude vlan_profile + performance_profile authkey plan_id ) ); if ( $conf->exists('svc_broadband-radius') ) { @@ -115,8 +116,6 @@ if ( $conf->exists('svc_broadband-radius') ) { } } -my $fixedblock = ''; - my $part_svc; my $svc_edit_callback = sub { @@ -124,8 +123,6 @@ my $svc_edit_callback = sub { $part_svc = $part_svc_x; #for field_callback to use - $opt->{'labels'}{'block_label'} = 'Block'; - my ($nas_export) = $part_svc->part_export('broadband_nas'); #can we assume there's only one of these per part_svc? if ( $nas_export ) { @@ -173,60 +170,13 @@ my $field_callback = sub { ? 'fixed' : 'hidden'; $fieldref->{'value'} = $columndef->columnvalue; - $fixedblock = $fieldref->{value} - if $fieldref->{field} eq 'blocknum'; - + if ( $fieldref->{field} eq 'usergroup' ) { $fieldref->{'formatted_value'} = [ $object->radius_groups('long_description') ]; } } - if ($object->svcnum) { - - $fieldref->{type} = 'hidden' - if $fieldref->{field} eq 'blocknum'; - - $fieldref->{value} = $object->addr_block->label - if $fieldref->{field} eq 'block_label' && $object->addr_block; - - } else { - - if ($fieldref->{field} eq 'block_label') { - if ($fixedblock && $object->addr_block) { - $object->blocknum($fixedblock); - $fieldref->{value} = $object->addr_block->label; - }else{ - $fieldref->{type} = 'hidden'; - } - } - - if ($fieldref->{field} eq 'blocknum') { - if ( $fixedblock or $conf->exists('auto_router') ) { - $fieldref->{type} = 'hidden'; - $fieldref->{value} = $fixedblock; - return; - } - - my $cust_pkg = qsearchs( 'cust_pkg', {pkgnum => $cgi->param('pkgnum')} ); - die "No cust_pkg entry!" unless $cust_pkg; - - $object->svcpart($part_svc->svcpart); - my @addr_block = - grep { ! $_->agentnum - || $cust_pkg->cust_main->agentnum == $_->agentnum - && $FS::CurrentUser::CurrentUser->agentnum($_->agentnum) - } - map { $_->addr_block } $object->allowed_routers; - my @options = map { $_->blocknum } - sort { $a->label cmp $b->label } @addr_block; - my %option_labels = map { ( $_->blocknum => $_->label ) } @addr_block; - $fieldref->{type} = 'select'; - $fieldref->{options} = \@options; - $fieldref->{labels} = \%option_labels; - } - - } }; diff --git a/httemplate/edit/svc_hardware.cgi b/httemplate/edit/svc_hardware.cgi index dcf83de37..d9cd4cd66 100644 --- a/httemplate/edit/svc_hardware.cgi +++ b/httemplate/edit/svc_hardware.cgi @@ -26,7 +26,8 @@ my @fields = ( }, { field => 'hw_addr', - type => 'text', + type => $conf->exists('svc_hardware-check_mac_addr') ? + 'input-mac_addr' : 'text', label => 'Hardware address', }, { diff --git a/httemplate/elements/masked_input_1.1.js b/httemplate/elements/masked_input_1.1.js new file mode 100644 index 000000000..05efa779b --- /dev/null +++ b/httemplate/elements/masked_input_1.1.js @@ -0,0 +1,195 @@ +/*********************************************************************** + Masked Input version 1.1 +************************************************************************ +Author: Kendall Conrad +Home page: http://www.angelwatt.com/coding/masked_input.php +Created: 2008-12-16 +Modified: 2010-04-14 +Description: +License: This work is licensed under a Creative Commons Attribution-Share Alike + 3.0 United States License http://creativecommons.org/licenses/by-sa/3.0/us/ + +Argument pieces: +- elm: [req] text input node to apply the mask on +- format: [req] string format for the mask +- allowed: [opt, '0123456789'] string with chars allowed to be typed +- sep: [opt, '\/:-'] string of char(s) used as separators in mask +- typeon: [opt, '_YMDhms'] string of chars in mask that can be typed on +- onbadkey: [opt, null] function to run when user types a unallowed key +- badkeywait: [opt, 0] used with onbadkey. Indicates how long (in ms) to lock + text input for onbadkey function to run +***********************************************************************/ +function MaskedInput(args) +{ + if (args['elm'] === null || args['format'] === null) { return false; } + var el = args['elm'], + format = args['format'], + allowed = args['allowed'] || '0123456789', + sep = args['separator'] || '\/:-', + open = args['typeon'] || '_YMDhms', + onbadkey = args['onbadkey'] || function(){}, + badwait = args['badkeywait'] || 0; + + var locked = false, hold = 0; + el.value = format; + // Assign events + el.onkeydown = KeyHandlerDown; // + el.onkeypress = KeyHandlerPress; // add event handlers to element + el.onkeyup = KeyHandlerUp; // + + function GetKey(code) + { + code = code || window.event, ch = ''; + var keyCode = code.which, evt = code.type; + if (keyCode == null) { keyCode = code.keyCode; } + if (keyCode === null) { return ''; } // no key, no play + // deal with special keys + switch (keyCode) { + case 8: ch = 'bksp'; break; + case 46: // handle del and . both being 46 + ch = (evt == 'keydown') ? 'del' : '.'; break; + case 16: ch = 'shift'; break;//shift + case 0:/*CRAP*/ case 9:/*TAB*/ case 13:/*ENTER*/ + ch = 'etc'; break; + case 37: case 38: case 39: case 40: // arrow keys + ch = (!code.shiftKey && + (code.charCode != 39 && code.charCode !== undefined)) ? + 'etc' : String.fromCharCode(keyCode); + break; + // default to thinking it's a character or digit + default: ch = String.fromCharCode(keyCode); + } + return ch; + } + function KeyHandlerDown(e) + { + e = e || event; + if (locked) { return false; } + var key = GetKey(e); + if (el.value == '') { el.value = format; SetTextCursor(el,0); } + // Only do update for bksp del + if (key == 'bksp' || key == 'del') { Update(key); return false; } + else if (key == 'etc' || key == 'shift') { return true; } + else { return true; } + } + function KeyHandlerPress(e) + { + e = e || event; + if (locked) { return false; } + var key = GetKey(e); + // Check if modifier key is being pressed; command + if (key=='etc' || e.metaKey || e.ctrlKey || e.altKey) { return true; } + if (key != 'bksp' && key != 'del' && key != 'etc' && key != 'shift') { + if (!GoodOnes(key)) { return false; } + return Update(key); + } + else { return false; } + } + function KeyHandlerUp(e) { hold = 0; } + function Update(key) + { + var p = GetTextCursor(el), c = el.value, val = ''; + // Handle keys now + switch (true) { + case (allowed.indexOf(key) != -1): + if (++p > format.length) { return false; } // if text csor at end + // Handle cases where user places csor before separator + while (sep.indexOf(c.charAt(p-1)) != -1 && p <= format.length) { p++; } + val = c.substr(0, p-1) + key + c.substr(p); + // Move csor up a spot if next char is a separator char + if (allowed.indexOf(c.charAt(p)) == -1 + && open.indexOf(c.charAt(p)) == -1) { p++; } + break; + case (key=='bksp'): // backspace + if (--p < 0) return false; // at start of field + // If previous char is a separator, move a little more + while (allowed.indexOf(c.charAt(p)) == -1 + && open.indexOf(c.charAt(p)) == -1 + && p > 1) { p--; } + val = c.substr(0, p) + format.substr(p,1) + c.substr(p+1); + break; + case (key=='del'): // forward delete + if (p >= c.length) { return false; } // at end of field + // If next char is a separator and not the end of the text field + while (sep.indexOf(c.charAt(p)) != -1 + && c.charAt(p) != '') { p++; } + val = c.substr(0, p) + format.substr(p,1) + c.substr(p+1); + p++; // Move position forward + break; + case (key=='etc'): return true; // Catch other allowed chars + default: return false; // Ignore the rest + } + el.value = ''; // blank it first (Firefox issue) + el.value = val; // put updated value back in + SetTextCursor(el, p); // Set the text cursor + return false; + } + function GetTextCursor(node) + { + try { + if (node.selectionStart >= 0) { return node.selectionStart; } + else if (document.selection) {// IE + var ntxt = node.value; // getting starting text + var rng = document.selection.createRange(); + rng.text = '|%|'; + var start = node.value.indexOf('|%|'); + rng.moveStart('character', -3); + rng.text = ''; + // put starting text back in, + // fixes issue if all text was highlighted + node.value = ntxt; + return start; + } return -1; + } catch(e) { return false; } + } + function SetTextCursor(node, pos) + { + try { + if (node.selectionStart) { + node.focus(); + node.setSelectionRange(pos,pos); + } + else if (node.createTextRange) { // IE + var rng = node.createTextRange(); + rng.move('character', pos); + rng.select(); + } + } catch(e) { return false; } + } + function GoodOnes(k) + { + if (allowed.indexOf(k) == -1 && k!='bksp' && k!='del' && k!='etc') { + var p = GetTextCursor(el); // Need to ensure cursor position not lost + locked = true; onbadkey(); + // Hold lock long enough for onbadkey function to run + setTimeout(function(){locked=false; SetTextCursor(el,p);}, badwait); + return false; + } return true; + } + function resetField() { + el.value = format; + } + function setAllowed(a) { + allowed = a; + resetField(); + } + function setFormat(f) { + format = f; + resetField(); + } + function setSeparator(s) { + sep = s; + resetField(); + } + function setTypeon(t) { + open = t; + resetField(); + } + return { + resetField:resetField, + setAllowed:setAllowed, + setFormat:setFormat, + setSeparator:setSeparator, + setTypeon:setTypeon + } +} diff --git a/httemplate/elements/tr-input-mac_addr.html b/httemplate/elements/tr-input-mac_addr.html new file mode 100644 index 000000000..d768d4e71 --- /dev/null +++ b/httemplate/elements/tr-input-mac_addr.html @@ -0,0 +1,11 @@ +<& /elements/tr-input-mask.html, + format => '__:__:__:__:__:__', + allowed => '0123456789ABCDEFabcdef', + %opt, +&> +<%init> +my %opt = @_; +my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value}; +$value =~ s/\W//g; +$opt{curr_value} = join(':', $value =~ /../g); + diff --git a/httemplate/elements/tr-input-mask.html b/httemplate/elements/tr-input-mask.html new file mode 100644 index 000000000..33725b9a5 --- /dev/null +++ b/httemplate/elements/tr-input-mask.html @@ -0,0 +1,41 @@ +% if ( !$init ) { + +% $init++; +% } +<& /elements/tr-input-text.html, id => $id, @_ &> + +<%shared> +my $init = 0; + +<%init> +my %opt = @_; +# must have a DOM id +my $id = $opt{id} || sprintf('input%04d',int(rand(10000))); +my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value} || ''; + +<%doc> +Set up a text input field with input masking. + +<& /elements/tr-input-mask.html, + format => '____-__-__', + #typeon => '_YMDhms', # which characters in the format represent blanks + #allowed => '0123456789', # characters allowed in the blanks + ... all other options as for tr-input-text.html +&> + +Note that the value sent on form submission will contain the mask +separators, and if value/curr_value is passed, it should also be +formatted to fit the mask. + +Uses masked_input_1.1.js by Kendall Conrad, available under a Creative Commons +Attribution-ShareAlike license. + diff --git a/httemplate/elements/tr-select-router_block_ip.html b/httemplate/elements/tr-select-router_block_ip.html index 45d1dacd8..ed8fe810f 100644 --- a/httemplate/elements/tr-select-router_block_ip.html +++ b/httemplate/elements/tr-select-router_block_ip.html @@ -1,14 +1,22 @@ <& /elements/tr-td-label.html, label => ($opt{'label'} || 'Router') &> @@ -19,7 +27,7 @@ function hide_if_auto_addr(obj, i) { records => \@routers, name_col => 'routername', value_col => 'routernum', - onchange => 'hide_if_auto_addr', + onchange => 'lock_ip_addr', curr_value=> $opt{'routernum'}, }, { @@ -44,17 +52,18 @@ function hide_if_auto_addr(obj, i) { % } % else { + value="<% $opt{'ip_addr'} |h%>"> % } - <% $opt{'ip_addr'} || '' %> - (automatic) + <%init> + my %opt = @_; my @routers; +my $conf = FS::Conf->new; my $svc_x = $opt{'object'}; if ( $svc_x ) { @@ -91,5 +100,17 @@ else { @routers = qsearch('router', {}); } -my %auto_addr_routernum = map { $_->routernum, $_->auto_addr } @routers; +my %manual_addr_routernum = map { $_->routernum, $_->manual_addr } @routers; + +if ( $conf->exists('auto_router') ) { + # Then show an "(automatic)" router, with no blocks. manual_addr is on + # so that the ip_addr field will be unlocked. + unshift @routers, FS::router->new({ + 'routernum' => '', + 'routername' => '(automatic)', + 'manual_addr' => 'Y', + }); + $manual_addr_routernum{''} = 'Y'; +} + diff --git a/httemplate/view/part_event-targets.html b/httemplate/view/part_event-targets.html index c5faccfd6..2029fd4bc 100644 --- a/httemplate/view/part_event-targets.html +++ b/httemplate/view/part_event-targets.html @@ -3,6 +3,16 @@ 'title' => 'Event query - '.$part_event->event, } &> +

url%> METHOD="GET"> +When event is run on <& /elements/input-date-field.html, { + 'name' => 'date', + 'value' => $time, + 'format' => FS::Conf->new->config('date_format') || '%m/%d/%Y', +} &> + + +
+

% if ( $objects > 0 ) { <% emt("[quant,_1,$label]", $objects) %> % if ( $part_event->eventtable ne 'cust_main' ) { @@ -18,8 +28,8 @@ % my @rowcolors = ('ffffff','eeeeee'); % my $row = 0; - % foreach my $object (@targets) { + % # now works for all eventtables, including cust_pkg % my $link = $p . 'view/' . $part_event->eventtable . '.cgi?' . % $object->$pkey; @@ -65,12 +75,14 @@ die "access denied" unless $curuser->access_right('Edit billing events') || $curuser->access_right('Edit global billing events'); -my ($eventpart) = $cgi->keywords; +my ($eventpart) = $cgi->param('eventpart'); $eventpart =~ /^\d+$/ or die 'illegal eventpart'; +my $time = parse_datetime($cgi->param('date')) || time; + my $part_event = FS::part_event->by_key($eventpart) or die "Event definition $eventpart not found.\n"; -my @targets = $part_event->targets; +my @targets = $part_event->targets('time' => $time); my $total = @targets; # in imitation of search/elements/search-html.html diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index 961374e7d..131582f1c 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -17,7 +17,6 @@ my %labels = map { $_ => ( ref($fields->{$_}) #my %labels = (); $labels{'description'} = emt('Description'); -$labels{'router'} = emt('Router'); $labels{'speed_down'} = emt('Download Speed'); $labels{'speed_up'} = emt('Upload Speed'); $labels{'ip_addr'} = emt('IP Address'); @@ -32,7 +31,7 @@ my @fields = ( 'speed_up', { field => 'ip_addr', value => \&ip_addr }, { field => 'sectornum', value => \§ornum }, - 'mac_addr', + { field => 'mac_addr', value => \&mac_addr }, #'latitude', #'longitude', { field => 'coordinates', value => \&coordinates }, @@ -67,6 +66,11 @@ sub ip_addr { $out; } +sub mac_addr { + my $svc = shift; + join(':', $svc->mac_addr =~ /../g); +} + sub usergroup { my $svc = shift; my $usergroup = $svc->usergroup; diff --git a/httemplate/view/svc_hardware.cgi b/httemplate/view/svc_hardware.cgi index 1d882352b..725358cad 100644 --- a/httemplate/view/svc_hardware.cgi +++ b/httemplate/view/svc_hardware.cgi @@ -6,6 +6,7 @@ %> <%init> +my $conf = new FS::Conf; my $fields = FS::svc_hardware->table_info->{'fields'}; my %labels = map { $_ => ( ref($fields->{$_}) ? $fields->{$_}{'label'} @@ -24,5 +25,22 @@ my $note = { field => 'note', type => 'text', value => sub { encode_entities($_[0]->note) } }; -my @fields = ($model, qw( serial hw_addr ip_addr smartcard ), $status, $note ); +my $hw_addr ={ field => 'hw_addr', + type => 'text', + value => sub { + my $hw_addr = $_[0]->hw_addr; + $conf->exists('svc_hardware-check_mac_addr') ? + join(':', $hw_addr =~ /../g) : $hw_addr + }, + }; + +my @fields = ( + $model, + 'serial', + $hw_addr, + 'ip_addr', + 'smartcard', + $status, + $note, +);