package FS::svc_acct;
+use base qw( FS::svc_Domain_Mixin FS::svc_PBX_Mixin
+ FS::svc_CGP_Mixin FS::svc_CGPRule_Mixin
+ FS::svc_Radius_Mixin
+ FS::svc_Tower_Mixin
+ FS::svc_IP_Mixin
+ FS::svc_Common
+ );
use strict;
-use vars qw( @ISA $DEBUG $me $conf $skip_fuzzyfiles
+use vars qw( $DEBUG $me $conf $skip_fuzzyfiles
$dir_prefix @shells $usernamemin
$usernamemax $passwordmin $passwordmax
$username_ampersand $username_letter $username_letterfirst
$username_noperiod $username_nounderscore $username_nodash
$username_uppercase $username_percent $username_colon
+ $username_slash $username_equals $username_pound
+ $username_exclamation
$password_noampersand $password_noexclamation
$warning_template $warning_from $warning_subject $warning_mimetype
$warning_cc
use Fcntl qw(:flock);
use Date::Format;
use Crypt::PasswdMD5 1.2;
-use Digest::SHA1 'sha1_base64';
+use Digest::SHA 'sha1_base64';
use Digest::MD5 'md5_base64';
use Data::Dumper;
use Text::Template;
use FS::Msgcat qw(gettext);
use FS::UI::bytecount;
use FS::UI::Web;
+use FS::PagedSearch qw( psearch ); # XXX in v4, replace with FS::Cursor
use FS::part_pkg;
-use FS::svc_Common;
-use FS::cust_svc;
use FS::part_svc;
use FS::svc_acct_pop;
use FS::cust_main_invoice;
use FS::svc_domain;
+use FS::svc_pbx;
use FS::raddb;
use FS::queue;
use FS::radius_usergroup;
+use FS::radius_group;
use FS::export_svc;
use FS::part_export;
use FS::svc_forward;
use FS::svc_www;
use FS::cdr;
-
-@ISA = qw( FS::svc_Common );
+use FS::tower_sector;
$DEBUG = 0;
$me = '[FS::svc_acct]';
$username_ampersand = $conf->exists('username-ampersand');
$username_percent = $conf->exists('username-percent');
$username_colon = $conf->exists('username-colon');
+ $username_slash = $conf->exists('username-slash');
+ $username_equals = $conf->exists('username-equals');
+ $username_pound = $conf->exists('username-pound');
+ $username_exclamation = $conf->exists('username-exclamation');
$password_noampersand = $conf->exists('password-noexclamation');
$password_noexclamation = $conf->exists('password-noexclamation');
$dirhash = $conf->config('dirhash') || 0;
);
@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
-@pw_set = ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '!', '.', ',' );
+@pw_set = ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '.', ',' );
sub _cache {
my $self = shift;
=over 4
-=item svcnum - primary key (assigned automatcially for new accounts)
+=item svcnum
+
+Primary key (assigned automatcially for new accounts)
=item username
-=item _password - generated if blank
+=item _password
+
+generated if blank
+
+=item _password_encoding
-=item _password_encoding - plain, crypt, ldap (or empty for autodetection)
+plain, crypt, ldap (or empty for autodetection)
-=item sec_phrase - security phrase
+=item sec_phrase
-=item popnum - Point of presence (see L<FS::svc_acct_pop>)
+security phrase
+
+=item popnum
+
+Point of presence (see L<FS::svc_acct_pop>)
=item uid
=item gid
-=item finger - GECOS
+=item finger
+
+GECOS
+
+=item dir
-=item dir - set automatically if blank (and uid is not)
+set automatically if blank (and uid is not)
=item shell
-=item quota - (unimplementd)
+=item quota
-=item slipip - IP address
+=item slipip
+
+IP address
+
+=item seconds
+
+=item upbytes
+
+=item downbyte
+
+=item totalbytes
+
+=item domsvc
-=item seconds -
+svcnum from svc_domain
-=item upbytes -
+=item pbxsvc
-=item downbytes -
+Optional svcnum from svc_pbx
-=item totalbytes -
+=item radius_I<Radius_Attribute>
-=item domsvc - svcnum from svc_domain
+I<Radius-Attribute> (reply)
-=item radius_I<Radius_Attribute> - I<Radius-Attribute> (reply)
+=item rc_I<Radius_Attribute>
-=item rc_I<Radius_Attribute> - I<Radius-Attribute> (check)
+I<Radius-Attribute> (check)
=back
'sorts' => [ 'username', 'uid', 'seconds', 'last_login' ],
'display_weight' => 10,
'cancel_weight' => 50,
+ 'ip_field' => 'slipip',
+ 'manual_require' => 1,
'fields' => {
'dir' => 'Home directory',
'uid' => {
disable_default => 1,
disable_fixed => 1,
disable_select => 1,
+ required => 1,
},
+ 'password_selfchange' => { label => 'Password modification',
+ type => 'checkbox',
+ },
+ 'password_recover' => { label => 'Password recovery',
+ type => 'checkbox',
+ },
'quota' => {
- label => 'Quota',
+ label => 'Quota', #Mail storage limit
+ type => 'text',
+ disable_inventory => 1,
+ },
+ 'file_quota'=> {
+ label => 'File storage limit',
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
- '_password' => 'Password',
+ 'file_maxnum'=> {
+ label => 'Number of files limit',
+ type => 'text',
+ disable_inventory => 1,
+ },
+ 'file_maxsize'=> {
+ label => 'File size limit',
+ type => 'text',
+ disable_inventory => 1,
+ },
+ '_password' => { label => 'Password',
+ required => 1
+ },
'gid' => {
label => 'GID',
def_info => 'when blank, defaults to UID',
select_key => 'svcnum',
select_label => 'domain',
disable_inventory => 1,
-
+ required => 1,
+ },
+ 'pbxsvc' => { label => 'PBX',
+ type => 'select-svc_pbx.html',
+ disable_inventory => 1,
+ disable_select => 1, #UI wonky, pry works otherwise
},
+ 'sectornum' => 'Tower sector',
'usergroup' => {
label => 'RADIUS groups',
- type => 'radius_usergroup_selector',
+ type => 'select-radius_group.html',
disable_inventory => 1,
disable_select => 1,
+ multiple => 1,
},
'seconds' => { label => 'Seconds',
label_sort => 'with Time Remaining',
label => 'Last logout',
type => 'disabled',
},
+
+ 'cgp_aliases' => {
+ label => 'Communigate aliases',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ #settings
+ 'cgp_type'=> {
+ label => 'Communigate account type',
+ type => 'select',
+ select_list => [qw( MultiMailbox TextMailbox MailDirMailbox AGrade BGrade CGrade )],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_accessmodes' => {
+ label => 'Communigate enabled services',
+ type => 'communigate_pro-accessmodes',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_rulesallowed' => {
+ label => 'Allowed mail rules',
+ type => 'select',
+ select_list => [ '', 'No', 'Filter Only', 'All But Exec', 'Any' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_rpopallowed' => { label => 'RPOP modifications',
+ type => 'checkbox',
+ },
+ 'cgp_mailtoall' => { label => 'Accepts mail to "all"',
+ type => 'checkbox',
+ },
+ 'cgp_addmailtrailer' => { label => 'Add trailer to sent mail',
+ type => 'checkbox',
+ },
+ 'cgp_archiveafter' => {
+ label => 'Archive messages after',
+ type => 'select',
+ select_hash => [
+ -2 => 'default(730 days)',
+ 0 => 'Never',
+ 86400 => '24 hours',
+ 172800 => '2 days',
+ 259200 => '3 days',
+ 432000 => '5 days',
+ 604800 => '7 days',
+ 1209600 => '2 weeks',
+ 2592000 => '30 days',
+ 7776000 => '90 days',
+ 15552000 => '180 days',
+ 31536000 => '365 days',
+ 63072000 => '730 days',
+ ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ #XXX mailing lists
+
+ #preferences
+ 'cgp_deletemode' => {
+ label => 'Communigate message delete method',
+ type => 'select',
+ select_list => [ 'Move To Trash', 'Immediately', 'Mark' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_emptytrash' => {
+ label => 'Communigate on logout remove trash',
+ type => 'select',
+ select_list => __PACKAGE__->cgp_emptytrash_values,
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_language' => {
+ label => 'Communigate language',
+ type => 'select',
+ select_list => [ '', qw( English Arabic Chinese Dutch French German Hebrew Italian Japanese Portuguese Russian Slovak Spanish Thai ) ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_timezone' => {
+ label => 'Communigate time zone',
+ type => 'select',
+ select_list => __PACKAGE__->cgp_timezone_values,
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_skinname' => {
+ label => 'Communigate layout',
+ type => 'select',
+ select_list => [ '', '***', 'GoldFleece', 'Skin2' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_prontoskinname' => {
+ label => 'Communigate Pronto style',
+ type => 'select',
+ select_list => [ '', 'Pronto', 'Pronto-darkflame', 'Pronto-steel', 'Pronto-twilight', ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_sendmdnmode' => {
+ label => 'Communigate send read receipts',
+ type => 'select',
+ select_list => [ '', 'Never', 'Manually', 'Automatically' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+
+ #mail
+ #XXX RPOP settings
+
},
};
}
sub table_dupcheck_fields { ( 'username', 'domsvc' ); }
-sub _fieldhandlers {
- {
- #false laziness with edit/svc_acct.cgi
- 'usergroup' => sub {
- my( $self, $groups ) = @_;
- if ( ref($groups) eq 'ARRAY' ) {
- $groups;
- } elsif ( length($groups) ) {
- [ split(/\s*,\s*/, $groups) ];
- } else {
- [];
- }
- },
- };
-}
-
sub last_login {
shift->_lastlog('in', @_);
}
$class->search_sql_field('username', $string ).
' ) ';
} else {
- ' ( '.
- $class->search_sql_field('username', $string).
- ( $string =~ /^\d+$/
- ? 'OR '. $class->search_sql_field('svcnum', $string)
- : ''
- ).
- ' ) ';
+ $class->search_sql_field('username', $string);
}
}
my $dbh = dbh;
my @jobnums;
- my $error = $self->SUPER::insert(
+ my $error = $self->SUPER::insert( # usergroup is here
'jobnums' => \@jobnums,
'child_objects' => $self->child_objects,
%options,
return $error;
}
- if ( $self->usergroup ) {
- foreach my $groupname ( @{$self->usergroup} ) {
- my $radius_usergroup = new FS::radius_usergroup ( {
- svcnum => $self->svcnum,
- groupname => $groupname,
- } );
- my $error = $radius_usergroup->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
- }
-
unless ( $skip_fuzzyfiles ) {
$error = $self->queue_fuzzyfiles_update;
if ( $error ) {
}
#welcome email
- my ($to,$welcome_template,$welcome_from,$welcome_subject,$welcome_subject_template,$welcome_mimetype)
- = ('','','','','','');
-
- if ( $conf->exists('welcome_email', $agentnum) ) {
- $welcome_template = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", $conf->config('welcome_email', $agentnum) ]
- ) or warn "can't create welcome email template: $Text::Template::ERROR";
- $welcome_from = $conf->config('welcome_email-from', $agentnum);
- # || 'your-isp-is-dum'
- $welcome_subject = $conf->config('welcome_email-subject', $agentnum)
- || 'Welcome';
- $welcome_subject_template = new Text::Template (
- TYPE => 'STRING',
- SOURCE => $welcome_subject,
- ) or warn "can't create welcome email subject template: $Text::Template::ERROR";
- $welcome_mimetype = $conf->config('welcome_email-mimetype', $agentnum)
- || 'text/plain';
- }
- if ( $welcome_template && $cust_pkg ) {
- my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
- if ( $to ) {
-
- my %hash = (
- 'custnum' => $self->custnum,
- 'username' => $self->username,
- 'password' => $self->_password,
- 'first' => $cust_main->first,
- 'last' => $cust_main->getfield('last'),
- 'pkg' => $cust_pkg->part_pkg->pkg,
- );
- my $wqueue = new FS::queue {
- 'svcnum' => $self->svcnum,
- 'job' => 'FS::svc_acct::send_email'
- };
- my $error = $wqueue->insert(
- 'to' => $to,
- 'from' => $welcome_from,
- 'subject' => $welcome_subject_template->fill_in( HASH => \%hash, ),
- 'mimetype' => $welcome_mimetype,
- 'body' => $welcome_template->fill_in( HASH => \%hash, ),
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error queuing welcome email: $error";
- }
-
- if ( $options{'depend_jobnum'} ) {
- warn "$me depend_jobnum found; adding to welcome email dependancies"
- if $DEBUG;
- if ( ref($options{'depend_jobnum'}) ) {
- warn "$me adding jobs ". join(', ', @{$options{'depend_jobnum'}} ).
- "to welcome email dependancies"
- if $DEBUG;
- push @jobnums, @{ $options{'depend_jobnum'} };
- } else {
- warn "$me adding job $options{'depend_jobnum'} ".
- "to welcome email dependancies"
- if $DEBUG;
- push @jobnums, $options{'depend_jobnum'};
- }
+ my @welcome_exclude_svcparts = $conf->config('svc_acct_welcome_exclude');
+ unless ( grep { $_ eq $self->svcpart } @welcome_exclude_svcparts ) {
+ my $error = '';
+ my $msgnum = $conf->config('welcome_msgnum', $agentnum);
+ if ( $msgnum ) {
+ my $msg_template = qsearchs('msg_template', { msgnum => $msgnum });
+ $error = $msg_template->send('cust_main' => $cust_main,
+ 'object' => $self);
}
-
- foreach my $jobnum ( @jobnums ) {
- my $error = $wqueue->depend_insert($jobnum);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error queuing welcome email job dependancy: $error";
+ else { #!$msgnum
+ my ($to,$welcome_template,$welcome_from,$welcome_subject,$welcome_subject_template,$welcome_mimetype)
+ = ('','','','','','');
+
+ if ( $conf->exists('welcome_email', $agentnum) ) {
+ $welcome_template = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", $conf->config('welcome_email', $agentnum) ]
+ ) or warn "can't create welcome email template: $Text::Template::ERROR";
+ $welcome_from = $conf->config('welcome_email-from', $agentnum);
+ # || 'your-isp-is-dum'
+ $welcome_subject = $conf->config('welcome_email-subject', $agentnum)
+ || 'Welcome';
+ $welcome_subject_template = new Text::Template (
+ TYPE => 'STRING',
+ SOURCE => $welcome_subject,
+ ) or warn "can't create welcome email subject template: $Text::Template::ERROR";
+ $welcome_mimetype = $conf->config('welcome_email-mimetype', $agentnum)
+ || 'text/plain';
}
- }
-
- }
-
+ if ( $welcome_template ) {
+ my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
+ if ( $to ) {
+
+ my %hash = (
+ 'custnum' => $self->custnum,
+ 'username' => $self->username,
+ 'password' => $self->_password,
+ 'first' => $cust_main->first,
+ 'last' => $cust_main->getfield('last'),
+ 'pkg' => $cust_pkg->part_pkg->pkg,
+ );
+ my $wqueue = new FS::queue {
+ 'svcnum' => $self->svcnum,
+ 'job' => 'FS::svc_acct::send_email'
+ };
+ my $error = $wqueue->insert(
+ 'to' => $to,
+ 'from' => $welcome_from,
+ 'subject' => $welcome_subject_template->fill_in( HASH => \%hash, ),
+ 'mimetype' => $welcome_mimetype,
+ 'body' => $welcome_template->fill_in( HASH => \%hash, ),
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error queuing welcome email: $error";
+ }
+
+ if ( $options{'depend_jobnum'} ) {
+ warn "$me depend_jobnum found; adding to welcome email dependancies"
+ if $DEBUG;
+ if ( ref($options{'depend_jobnum'}) ) {
+ warn "$me adding jobs ". join(', ', @{$options{'depend_jobnum'}} ).
+ "to welcome email dependancies"
+ if $DEBUG;
+ push @jobnums, @{ $options{'depend_jobnum'} };
+ } else {
+ warn "$me adding job $options{'depend_jobnum'} ".
+ "to welcome email dependancies"
+ if $DEBUG;
+ push @jobnums, $options{'depend_jobnum'};
+ }
+ }
+
+ foreach my $jobnum ( @jobnums ) {
+ my $error = $wqueue->depend_insert($jobnum);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error queuing welcome email job dependancy: $error";
+ }
+ }
+
+ }
+
+ } # if $welcome_template
+ } # if !$msgnum
}
-
- } # if ( $cust_pkg )
+ } # if $cust_pkg
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
''; #no error
}
# set usage fields and thresholds if unset but set in a package def
+# AND the package already has a last bill date (otherwise they get double added)
sub preinsert_hook_first {
my $self = shift;
return '' unless $self->pkgnum;
my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
- my $part_pkg = $cust_pkg->part_pkg if $cust_pkg;
+ return '' unless $cust_pkg && $cust_pkg->last_bill;
+
+ my $part_pkg = $cust_pkg->part_pkg;
return '' unless $part_pkg && $part_pkg->can('usage_valuehash');
my %values = $part_pkg->usage_valuehash;
}
}
- my $error = $self->SUPER::delete;
+ my $error = $self->SUPER::delete; # usergroup here
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
- foreach my $radius_usergroup (
- qsearch('radius_usergroup', { 'svcnum' => $self->svcnum } )
- ) {
- my $error = $radius_usergroup->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
}
}
+ return "can't change username"
+ if $old->username ne $new->username
+ && $conf->exists('svc_acct-no_edit_username');
+
#change homdir when we change username
$new->setfield('dir', '') if $old->username ne $new->username;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- # redundant, but so $new->usergroup gets set
- $error = $new->check;
- return $error if $error;
-
- $old->usergroup( [ $old->radius_groups ] );
- if ( $DEBUG ) {
- warn $old->email. " old groups: ". join(' ',@{$old->usergroup}). "\n";
- warn $new->email. "new groups: ". join(' ',@{$new->usergroup}). "\n";
- }
- if ( $new->usergroup ) {
- #(sorta) false laziness with FS::part_export::sqlradius::_export_replace
- my @newgroups = @{$new->usergroup};
- foreach my $oldgroup ( @{$old->usergroup} ) {
- if ( grep { $oldgroup eq $_ } @newgroups ) {
- @newgroups = grep { $oldgroup ne $_ } @newgroups;
- next;
- }
- my $radius_usergroup = qsearchs('radius_usergroup', {
- svcnum => $old->svcnum,
- groupname => $oldgroup,
- } );
- my $error = $radius_usergroup->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error deleting radius_usergroup $oldgroup: $error";
- }
- }
-
- foreach my $newgroup ( @newgroups ) {
- my $radius_usergroup = new FS::radius_usergroup ( {
- svcnum => $new->svcnum,
- groupname => $newgroup,
- } );
- my $error = $radius_usergroup->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error adding radius_usergroup $newgroup: $error";
- }
- }
-
- }
-
- $error = $new->SUPER::replace($old, @_);
+ $error = $new->SUPER::replace($old, @_); # usergroup here
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error if $error;
my($recref) = $self->hashref;
- my $x = $self->setfixed( $self->_fieldhandlers );
+ my $x = $self->setfixed;
return $x unless ref($x);
my $part_svc = $x;
- if ( $part_svc->part_svc_column('usergroup')->columnflag eq "F" ) {
- $self->usergroup(
- [ split(',', $part_svc->part_svc_column('usergroup')->columnvalue) ] );
- }
-
my $error = $self->ut_numbern('svcnum')
#|| $self->ut_number('domsvc')
- || $self->ut_foreign_key('domsvc', 'svc_domain', 'svcnum' )
+ || $self->ut_foreign_key( 'domsvc', 'svc_domain', 'svcnum' )
+ || $self->ut_foreign_keyn('pbxsvc', 'svc_pbx', 'svcnum' )
+ || $self->ut_foreign_keyn('sectornum','tower_sector','sectornum')
+ || $self->ut_foreign_keyn('routernum','router','routernum')
+ || $self->ut_foreign_keyn('blocknum','addr_block','blocknum')
|| $self->ut_textn('sec_phrase')
|| $self->ut_snumbern('seconds')
|| $self->ut_snumbern('upbytes')
|| $self->ut_snumbern('downbytes')
|| $self->ut_snumbern('totalbytes')
- || $self->ut_enum( '_password_encoding',
- [ '', qw( plain crypt ldap ) ]
- )
+ || $self->ut_snumbern('seconds_threshold')
+ || $self->ut_snumbern('upbytes_threshold')
+ || $self->ut_snumbern('downbytes_threshold')
+ || $self->ut_snumbern('totalbytes_threshold')
+ || $self->ut_enum('_password_encoding', ['',qw(plain crypt ldap)])
+ || $self->ut_enum('password_selfchange', [ '', 'Y' ])
+ || $self->ut_enum('password_recover', [ '', 'Y' ])
+ #cardfortress
+ || $self->ut_anything('cf_privatekey')
+ #communigate
+ || $self->ut_textn('cgp_accessmodes')
+ || $self->ut_alphan('cgp_type')
+ || $self->ut_textn('cgp_aliases' ) #well
+ # settings
+ || $self->ut_alphasn('cgp_rulesallowed')
+ || $self->ut_enum('cgp_rpopallowed', [ '', 'Y' ])
+ || $self->ut_enum('cgp_mailtoall', [ '', 'Y' ])
+ || $self->ut_enum('cgp_addmailtrailer', [ '', 'Y' ])
+ || $self->ut_snumbern('cgp_archiveafter')
+ # preferences
+ || $self->ut_alphasn('cgp_deletemode')
+ || $self->ut_enum('cgp_emptytrash', $self->cgp_emptytrash_values)
+ || $self->ut_alphan('cgp_language')
+ || $self->ut_textn('cgp_timezone')
+ || $self->ut_textn('cgp_skinname')
+ || $self->ut_textn('cgp_prontoskinname')
+ || $self->ut_alphan('cgp_sendmdnmode')
;
return $error if $error;
+ # assign IP address, etc.
+ if ( $conf->exists('svc_acct-ip_addr') ) {
+ my $error = $self->svc_ip_check;
+ return $error if $error;
+ } else { # I think this is correct
+ $self->routernum('');
+ $self->blocknum('');
+ }
+
my $cust_pkg;
local $username_letter = $username_letter;
+ local $username_uppercase = $username_uppercase;
if ($self->svcnum) {
my $cust_svc = $self->cust_svc
or return "no cust_svc record found for svcnum ". $self->svcnum;
if ($cust_pkg) {
$username_letter =
$conf->exists('username-letter', $cust_pkg->cust_main->agentnum);
+ $username_uppercase =
+ $conf->exists('username-uppercase', $cust_pkg->cust_main->agentnum);
}
my $ulen = $usernamemax || $self->dbdef_table->column('username')->length;
- if ( $username_uppercase ) {
- $recref->{username} =~ /^([a-z0-9_\-\.\&\%\:]{$usernamemin,$ulen})$/i
- or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
- $recref->{username} = $1;
- } else {
- $recref->{username} =~ /^([a-z0-9_\-\.\&\%\:]{$usernamemin,$ulen})$/
- or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
- $recref->{username} = $1;
- }
+ $recref->{username} =~ /^([a-z0-9_\-\.\&\%\:\/\=\#\!]{$usernamemin,$ulen})$/i
+ or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
+ $recref->{username} = $1;
+
+ my $uerror = gettext('illegal_username'). ': '. $recref->{username};
+
+ unless ( $username_uppercase ) {
+ $recref->{username} =~ /[A-Z]/ and return $uerror;
+ }
if ( $username_letterfirst ) {
- $recref->{username} =~ /^[a-z]/ or return gettext('illegal_username');
+ $recref->{username} =~ /^[a-z]/ or return $uerror;
} elsif ( $username_letter ) {
- $recref->{username} =~ /[a-z]/ or return gettext('illegal_username');
+ $recref->{username} =~ /[a-z]/ or return $uerror;
}
if ( $username_noperiod ) {
- $recref->{username} =~ /\./ and return gettext('illegal_username');
+ $recref->{username} =~ /\./ and return $uerror;
}
if ( $username_nounderscore ) {
- $recref->{username} =~ /_/ and return gettext('illegal_username');
+ $recref->{username} =~ /_/ and return $uerror;
}
if ( $username_nodash ) {
- $recref->{username} =~ /\-/ and return gettext('illegal_username');
+ $recref->{username} =~ /\-/ and return $uerror;
}
unless ( $username_ampersand ) {
- $recref->{username} =~ /\&/ and return gettext('illegal_username');
+ $recref->{username} =~ /\&/ and return $uerror;
}
unless ( $username_percent ) {
- $recref->{username} =~ /\%/ and return gettext('illegal_username');
+ $recref->{username} =~ /\%/ and return $uerror;
}
unless ( $username_colon ) {
- $recref->{username} =~ /\:/ and return gettext('illegal_username');
+ $recref->{username} =~ /\:/ and return $uerror;
+ }
+ unless ( $username_slash ) {
+ $recref->{username} =~ /\// and return $uerror;
+ }
+ unless ( $username_equals ) {
+ $recref->{username} =~ /\=/ and return $uerror;
+ }
+ unless ( $username_pound ) {
+ $recref->{username} =~ /\#/ and return $uerror;
+ }
+ unless ( $username_exclamation ) {
+ $recref->{username} =~ /\!/ and return $uerror;
}
+
$recref->{popnum} =~ /^(\d*)$/ or return "Illegal popnum: ".$recref->{popnum};
$recref->{popnum} = $1;
return "Unknown popnum" unless
unless ( $part_svc->part_svc_column('dir')->columnflag eq 'F' ) {
- $recref->{dir} =~ /^([\/\w\-\.\&]*)$/
+ $recref->{dir} =~ /^([\/\w\-\.\&\:\#]*)$/
or return "Illegal directory: ". $recref->{dir};
$recref->{dir} = $1;
return "Illegal directory"
}
- # $error = $self->ut_textn('finger');
- # return $error if $error;
if ( $self->getfield('finger') eq '' ) {
my $cust_pkg = $self->svcnum
? $self->cust_svc->cust_pkg
$self->setfield('finger', $cust_main->first.' '.$cust_main->get('last') );
}
}
- $self->getfield('finger') =~
- /^([\w \t\!\@\#\$\%\&\(\)\-\+\;\'\"\,\.\?\/\*\<\>]*)$/
+ # $error = $self->ut_textn('finger');
+ # return $error if $error;
+ $self->getfield('finger') =~ /^([\w \,\.\-\'\&\t\!\@\#\$\%\(\)\+\;\"\?\/\*\<\>]*)$/
or return "Illegal finger: ". $self->getfield('finger');
$self->setfield('finger', $1);
- $recref->{quota} =~ /^(\w*)$/ or return "Illegal quota";
- $recref->{quota} = $1;
+ for (qw( quota file_quota file_maxsize )) {
+ $recref->{$_} =~ /^(\w*)$/ or return "Illegal $_";
+ $recref->{$_} = $1;
+ }
+ $recref->{file_maxnum} =~ /^\s*(\d*)\s*$/ or return "Illegal file_maxnum";
+ $recref->{file_maxnum} = $1;
unless ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) {
if ( $recref->{slipip} eq '' ) {
- $recref->{slipip} = '';
+ $recref->{slipip} = ''; # eh?
} elsif ( $recref->{slipip} eq '0e0' ) {
$recref->{slipip} = '0e0';
} else {
or return "Illegal slipip: ". $self->slipip;
$recref->{slipip} = $1;
}
-
}
#arbitrary RADIUS stuff; allow ut_textn for now
else {
return "invalid password encoding ('".$recref->{_password_encoding}."'";
}
+
$self->SUPER::check;
}
=cut
sub set_password {
- my $self = shift;
- my $pass = shift;
- my ($encoding, $encryption);
+ my( $self, $pass ) = ( shift, shift );
+
+ warn "[$me] set_password (to $pass) called on $self: ". Dumper($self)
+ if $DEBUG;
+
my $failure = gettext('illegal_password'). " $passwordmin-$passwordmax ".
FS::Msgcat::_gettext('illegal_password_characters').
": ". $pass;
- if(($passwordmin and length($pass) < $passwordmin) or
- ($passwordmax and length($pass) > $passwordmax)) {
- return $failure;
- }
+ my( $encoding, $encryption ) = ('', '');
- if($self->_password_encoding) {
+ if ( $self->_password_encoding ) {
$encoding = $self->_password_encoding;
# identify existing encryption method, try to use it.
$encryption = $self->_password_encryption;
- if(!$encryption) {
+ if (!$encryption) {
# use the system default
undef $encoding;
}
}
- if(!$encoding) {
+ if ( !$encoding ) {
# set encoding to system default
- ($encoding, $encryption) = split(/-/, lc($conf->config('default-password-encoding')));
+ ($encoding, $encryption) =
+ split(/-/, lc($conf->config('default-password-encoding') || ''));
$encoding ||= 'legacy';
$self->_password_encoding($encoding);
}
- if($encoding eq 'legacy') {
+ if ( $encoding eq 'legacy' ) {
+
# The legacy behavior from check():
# If the password is blank, randomize it and set encoding to 'plain'.
if(!defined($pass) or (length($pass) == 0 and $passwordmin)) {
$pass = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
$self->_password_encoding('plain');
- }
- else {
+ } else {
# Prefix + valid-length password
if ( $pass =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) {
$pass = $1.$3;
$self->_password_encoding('plain');
- }
# Prefix + crypt string
- elsif ( $pass =~ /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/ ) {
+ } elsif ( $pass =~ /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/ ) {
$pass = $1.$3;
$self->_password_encoding('crypt');
- }
# Various disabled crypt passwords
- elsif ( $pass eq '*' or
- $pass eq '!' or
- $pass eq '!!' ) {
+ } elsif ( $pass eq '*' || $pass eq '!' || $pass eq '!!' ) {
$self->_password_encoding('crypt');
- }
- else {
+ } else {
return $failure;
}
- }
+ }
+
+ $self->_password($pass);
+ return;
+
}
- elsif($encoding eq 'crypt') {
- if($encryption eq 'md5') {
+
+ return $failure
+ if $passwordmin && length($pass) < $passwordmin
+ or $passwordmax && length($pass) > $passwordmax;
+
+ if ( $encoding eq 'crypt' ) {
+ if ($encryption eq 'md5') {
$pass = unix_md5_crypt($pass);
- }
- elsif($encryption eq 'des') {
+ } elsif ($encryption eq 'des') {
$pass = crypt($pass, $saltset[int(rand(64))].$saltset[int(rand(64))]);
}
- }
- elsif($encoding eq 'ldap') {
- if($encryption eq 'md5') {
+
+ } elsif ( $encoding eq 'ldap' ) {
+ if ($encryption eq 'md5') {
$pass = md5_base64($pass);
- }
- elsif($encryption eq 'sha1') {
+ } elsif ($encryption eq 'sha1') {
$pass = sha1_base64($pass);
- }
- elsif($encryption eq 'crypt') {
+ } elsif ($encryption eq 'crypt') {
$pass = crypt($pass, $saltset[int(rand(64))].$saltset[int(rand(64))]);
}
# else $encryption eq 'plain', do nothing
+ $pass .= '=' x (4 - length($pass) % 4) #properly padded base64
+ if $encryption eq 'md5' || $encryption eq 'sha1';
$pass = '{'.uc($encryption).'}'.$pass;
}
# else encoding eq 'plain'
sub radius_password {
my $self = shift;
- my($pw_attrib, $password);
+ my $pw_attrib;
if ( $self->_password_encoding eq 'ldap' ) {
-
$pw_attrib = 'Password-With-Header';
- $password = $self->_password;
-
} elsif ( $self->_password_encoding eq 'crypt' ) {
-
$pw_attrib = 'Crypt-Password';
- $password = $self->_password;
-
} elsif ( $self->_password_encoding eq 'plain' ) {
-
- $pw_attrib = $radius_password; #Cleartext-Password? man rlm_pap
- $password = $self->_password;
-
+ $pw_attrib = $radius_password;
} else {
-
- $pw_attrib = length($password) <= 12 ? $radius_password : 'Crypt-Password';
- $password = $self->_password;
-
+ $pw_attrib = length($self->_password) <= 12
+ ? $radius_password
+ : 'Crypt-Password';
}
- ($pw_attrib, $password);
+ ($pw_attrib, $self->_password);
}
$svc_domain->domain;
}
-=item svc_domain
-
-Returns the FS::svc_domain record for this account's domain (see
-L<FS::svc_domain>).
-
-=cut
-
-# FS::h_svc_acct has a history-aware svc_domain override
-
-sub svc_domain {
- my $self = shift;
- $self->{'_domsvc'}
- ? $self->{'_domsvc'}
- : qsearchs( 'svc_domain', { 'svcnum' => $self->domsvc } );
-}
-
=item cust_svc
Returns the FS::cust_svc record for this account (see L<FS::cust_svc>).
$self->username. '@'. $self->domain(@_);
}
+
=item acct_snarf
Returns an array of FS::acct_snarf records associated with the account.
-If the acct_snarf table does not exist or there are no associated records,
-an empty list is returned
=cut
-sub acct_snarf {
+# unused as originally intended, but now by Communigate Pro "RPOP"
+
+=item cgp_rpop_hashref
+
+Returns an arrayref of RPOP data suitable for Communigate Pro API commands.
+
+=cut
+
+sub cgp_rpop_hashref {
my $self = shift;
- return () unless dbdef->table('acct_snarf');
- eval "use FS::acct_snarf;";
- die $@ if $@;
- qsearch('acct_snarf', { 'svcnum' => $self->svcnum } );
+ { map { $_->snarfname => $_->cgp_hashref } $self->acct_snarf };
}
=item decrement_upbytes OCTETS
my $cust_pkg = $self->cust_svc->cust_pkg;
- my $conf_overlimit =
+ my @conf_overlimit =
$cust_pkg
? $conf->config('overlimit_groups', $cust_pkg->cust_main->agentnum )
: $conf->config('overlimit_groups');
foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
- my $groups = $conf_overlimit || $part_export->option('overlimit_groups');
- next unless $groups;
-
- my $gref = &{ $self->_fieldhandlers->{'usergroup'} }( $self, $groups );
+ my @groups = scalar(@conf_overlimit) ? @conf_overlimit
+ : split(' ',$part_export->option('overlimit_groups'));
+ next unless scalar(@groups);
my $other = new FS::svc_acct $self->hashref;
- $other->usergroup( $gref );
+ $other->usergroup(\@groups);
my($new,$old);
if ($action eq 'suspend') {
my $reset = 0;
my %handyhash = ();
if ( $options{null} ) {
- %handyhash = ( map { ( $_ => 'NULL', $_."_threshold" => 'NULL' ) }
+ %handyhash = ( map { ( $_ => undef, $_."_threshold" => undef ) }
qw( seconds upbytes downbytes totalbytes )
);
}
#die $error if $error; #services not explicity changed via the UI
my $sql = "UPDATE svc_acct SET " .
- join (',', map { "$_ = $handyhash{$_}" } (keys %handyhash) ).
+ join (',', map { "$_ = ?" } (keys %handyhash) ).
" WHERE svcnum = ". $self->svcnum;
warn "$me $sql\n"
if (scalar(keys %handyhash)) {
my $sth = $dbh->prepare( $sql )
or die "Error preparing $sql: ". $dbh->errstr;
- my $rv = $sth->execute();
+ my $rv = $sth->execute(values %handyhash);
die "Error executing $sql: ". $sth->errstr
unless defined($rv);
die "Can't update usage for svcnum ". $self->svcnum
$self->cust_svc->seconds_since(@_);
}
-=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
-
-Returns the numbers of seconds this account has been online between
-TIMESTAMP_START (inclusive) and TIMESTAMP_END (exclusive), according to an
-external SQL radacct table, specified via sqlradius export. Sessions which
-started in the specified range but are still open are counted from session
-start to the end of the range (unless they are over 1 day old, in which case
-they are presumed missing their stop record and not counted). Also, sessions
-which end in the range but started earlier are counted from the start of the
-range to session end. Finally, sessions which start before the range but end
-after are counted for the entire range.
-
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
-
-=cut
-
-#note: POD here, implementation in FS::cust_svc
-sub seconds_since_sqlradacct {
- my $self = shift;
- $self->cust_svc->seconds_since_sqlradacct(@_);
-}
-
-=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
-
-Returns the sum of the given attribute for all accounts (see L<FS::svc_acct>)
-in this package for sessions ending between TIMESTAMP_START (inclusive) and
-TIMESTAMP_END (exclusive).
+=item last_login_text
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
+Returns text describing the time of last login.
=cut
-#note: POD here, implementation in FS::cust_svc
-sub attribute_since_sqlradacct {
+sub last_login_text {
my $self = shift;
- $self->cust_svc->attribute_since_sqlradacct(@_);
+ $self->last_login ? ctime($self->last_login) : 'unknown';
}
-=item get_session_history TIMESTAMP_START TIMESTAMP_END
+=item psearch_cdrs OPTIONS
-Returns an array of hash references of this customers login history for the
-given time range. (document this better)
+Returns a paged search (L<FS::PagedSearch>) for Call Detail Records
+associated with this service. For svc_acct, "associated with" means that
+either the "src" or the "charged_party" field of the CDR matches the
+"username" field of the service.
=cut
-sub get_session_history {
- my $self = shift;
- $self->cust_svc->get_session_history(@_);
-}
-
-=item last_login_text
-
-Returns text describing the time of last login.
+sub psearch_cdrs {
+ my($self, %options) = @_;
+ my @fields;
+ my %hash;
+ my @where;
-=cut
+ my $did = dbh->quote($self->username);
-sub last_login_text {
- my $self = shift;
- $self->last_login ? ctime($self->last_login) : 'unknown';
-}
+ my $prefix = $options{'default_prefix'} || ''; #convergent.au '+61'
+ my $prefixdid = dbh->quote($prefix . $self->username);
-=item get_cdrs TIMESTAMP_START TIMESTAMP_END [ 'OPTION' => 'VALUE ... ]
+ my $for_update = $options{'for_update'} ? 'FOR UPDATE' : '';
-=cut
+ if ( $options{inbound} ) {
+ # these will be selected under their DIDs
+ push @where, "FALSE";
+ }
-sub get_cdrs {
- my($self, $start, $end, %opt ) = @_;
-
- my $did = $self->username; #yup
-
- my $prefix = $opt{'default_prefix'}; #convergent.au '+61'
-
- my $for_update = $opt{'for_update'} ? 'FOR UPDATE' : '';
-
- #SELECT $for_update * FROM cdr
- # WHERE calldate >= $start #need a conversion
- # AND calldate < $end #ditto
- # AND ( charged_party = "$did"
- # OR charged_party = "$prefix$did" #if length($prefix);
- # OR ( ( charged_party IS NULL OR charged_party = '' )
- # AND
- # ( src = "$did" OR src = "$prefix$did" ) # if length($prefix)
- # )
- # )
- # AND ( freesidestatus IS NULL OR freesidestatus = '' )
-
- my $charged_or_src;
- if ( length($prefix) ) {
- $charged_or_src =
- " AND ( charged_party = '$did'
- OR charged_party = '$prefix$did'
- OR ( ( charged_party IS NULL OR charged_party = '' )
- AND
- ( src = '$did' OR src = '$prefix$did' )
- )
- )
- ";
- } else {
- $charged_or_src =
- " AND ( charged_party = '$did'
- OR ( ( charged_party IS NULL OR charged_party = '' )
- AND
- src = '$did'
- )
- )
- ";
+ my @orwhere;
+ if (!$options{'disable_charged_party'}) {
+ push @orwhere,
+ "charged_party = $did",
+ "charged_party = $prefixdid";
+ }
+ if (!$options{'disable_src'}) {
+ push @orwhere,
+ "src = $did AND charged_party IS NULL",
+ "src = $prefixdid AND charged_party IS NULL";
+ }
+ push @where, '(' . join(' OR ', @orwhere) . ')';
+ # $options{'status'} = '' is meaningful; for the rest of them it's not
+ if ( exists $options{'status'} ) {
+ $hash{'freesidestatus'} = $options{'status'};
+ }
+ if ( $options{'cdrtypenum'} ) {
+ $hash{'cdrtypenum'} = $options{'cdrtypenum'};
}
+ if ( $options{'calltypenum'} ) {
+ $hash{'calltypenum'} = $options{'calltypenum'};
+ }
+ if ( $options{'begin'} ) {
+ push @where, 'startdate >= '. $options{'begin'};
+ }
+ if ( $options{'end'} ) {
+ push @where, 'startdate < '. $options{'end'};
+ }
+ if ( $options{'nonzero'} ) {
+ push @where, 'duration > 0';
+ }
- qsearch(
- 'select' => "$for_update *",
+ my $extra_sql = join(' AND ', @where);
+ if ($extra_sql) {
+ if (keys %hash) {
+ $extra_sql = " AND ".$extra_sql;
+ } else {
+ $extra_sql = " WHERE ".$extra_sql;
+ }
+ }
+ return psearch({
+ 'select' => '*',
'table' => 'cdr',
- 'hashref' => {
- #( freesidestatus IS NULL OR freesidestatus = '' )
- 'freesidestatus' => '',
- },
- 'extra_sql' => $charged_or_src,
-
- );
-
+ 'hashref' => \%hash,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => "ORDER BY startdate $for_update",
+ });
}
-=item radius_groups
+=item get_cdrs (DEPRECATED)
-Returns all RADIUS groups for this account (see L<FS::radius_usergroup>).
+Like psearch_cdrs, but returns all the L<FS::cdr> objects at once, in a
+single list. Arguments are the same as for psearch_cdrs.
=cut
-sub radius_groups {
+sub get_cdrs {
my $self = shift;
- if ( $self->usergroup ) {
- confess "explicitly specified usergroup not an arrayref: ". $self->usergroup
- unless ref($self->usergroup) eq 'ARRAY';
- #when provisioning records, export callback runs in svc_Common.pm before
- #radius_usergroup records can be inserted...
- @{$self->usergroup};
- } else {
- map { $_->groupname }
- qsearch('radius_usergroup', { 'svcnum' => $self->svcnum } );
- }
+ my $psearch = $self->psearch_cdrs(@_);
+ qsearch ( $psearch->{query} )
}
+# sub radius_groups has moved to svc_Radius_Mixin
+
=item clone_suspended
Constructor used by FS::part_export::_export_suspend fallback. Document
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' ) {
my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
if ( $encryption eq 'crypt' ) {
- crypt(
+ return crypt(
$self->_password,
$saltset[int(rand(64))].$saltset[int(rand(64))]
);
} elsif ( $encryption eq 'md5' ) {
- unix_md5_crypt( $self->_password );
+ return unix_md5_crypt( $self->_password );
} elsif ( $encryption eq 'blowfish' ) {
croak "unknown encryption method $encryption";
} else {
}
} elsif ( $self->_password =~ /^\{CRYPT\}(.+)$/ ) {
- $1;
+ return $1;
}
} elsif ( $self->_password_encoding eq 'crypt' ) {
my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
if ( $encryption eq 'crypt' ) {
- crypt(
+ return crypt(
$self->_password,
$saltset[int(rand(64))].$saltset[int(rand(64))]
);
} elsif ( $encryption eq 'md5' ) {
- unix_md5_crypt( $self->_password );
+ return unix_md5_crypt( $self->_password );
+ } elsif ( $encryption eq 'sha1_base64' ) { #for acct_sql
+ my $pass = sha1_base64( $self->_password );
+ $pass .= '=' x (4 - length($pass) % 4); #properly padded base64
+ return $pass;
} elsif ( $encryption eq 'blowfish' ) {
croak "unknown encryption method $encryption";
} else {
my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
if ( $encryption eq 'crypt' ) {
- crypt(
+ return crypt(
$self->_password,
$saltset[int(rand(64))].$saltset[int(rand(64))]
);
} elsif ( $encryption eq 'md5' ) {
- unix_md5_crypt( $self->_password );
+ return unix_md5_crypt( $self->_password );
} elsif ( $encryption eq 'blowfish' ) {
croak "unknown encryption method $encryption";
} else {
=cut
-sub search {
- my ($class, $params) = @_;
+sub _search_svc {
+ my( $class, $params, $from, $where ) = @_;
- my @where = ();
+ #these two should probably move to svc_Domain_Mixin ?
# domain
if ( $params->{'domain'} ) {
my $svc_domain = qsearchs('svc_domain', { 'domain'=>$params->{'domain'} } );
#preserve previous behavior & bubble up an error if $svc_domain not found?
- push @where, 'domsvc = '. $svc_domain->svcnum if $svc_domain;
+ push @$where, 'domsvc = '. $svc_domain->svcnum if $svc_domain;
}
# domsvc
if ( $params->{'domsvc'} =~ /^(\d+)$/ ) {
- push @where, "domsvc = $1";
- }
-
- #unlinked
- push @where, 'pkgnum IS NULL' if $params->{'unlinked'};
-
- #agentnum
- if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) {
- push @where, "agentnum = $1";
+ push @$where, "domsvc = $1";
}
- #custnum
- if ( $params->{'custnum'} =~ /^(\d+)$/ and $1 ) {
- push @where, "custnum = $1";
- }
-
- #pkgpart
- if ( $params->{'pkgpart'} && scalar(@{ $params->{'pkgpart'} }) ) {
- #XXX untaint or sql quote
- push @where,
- 'cust_pkg.pkgpart IN ('. join(',', @{ $params->{'pkgpart'} } ). ')';
- }
# popnum
if ( $params->{'popnum'} =~ /^(\d+)$/ ) {
- push @where, "popnum = $1";
+ push @$where, "popnum = $1";
}
- # svcpart
- if ( $params->{'svcpart'} =~ /^(\d+)$/ ) {
- push @where, "svcpart = $1";
- }
+ #and these in svc_Tower_Mixin, or maybe we never should have done svc_acct
+ # towers (or, as mark thought, never should have done svc_broadband)
- # here is the agent virtualization
- #if ($params->{CurrentUser}) {
- # my $access_user =
- # qsearchs('access_user', { username => $params->{CurrentUser} });
- #
- # if ($access_user) {
- # push @where, $access_user->agentnums_sql('table'=>'cust_main');
- # }else{
- # push @where, "1=0";
- # }
- #} else {
- push @where, $FS::CurrentUser::CurrentUser->agentnums_sql(
- 'table' => 'cust_main',
- 'null_right' => 'View/link unlinked services',
- );
- #}
-
- push @where, @{ $params->{'where'} } if $params->{'where'};
-
- my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
-
- my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
- ' LEFT JOIN part_svc USING ( svcpart ) '.
- ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
-
- my $count_query = "SELECT COUNT(*) FROM svc_acct $addl_from $extra_sql";
- #if ( keys %svc_acct ) {
- # $count_query .= ' WHERE '.
- # join(' AND ', map "$_ = ". dbh->quote($svc_acct{$_}),
- # keys %svc_acct
- # );
- #}
-
- my $sql_query = {
- 'table' => 'svc_acct',
- 'hashref' => {}, # \%svc_acct,
- 'select' => join(', ',
- 'svc_acct.*',
- 'part_svc.svc',
- 'cust_main.custnum',
- FS::UI::Web::cust_sql_fields($params->{'cust_fields'}),
- ),
- 'addl_from' => $addl_from,
- 'extra_sql' => $extra_sql,
- 'order_by' => $params->{'order_by'},
- 'count_query' => $count_query,
- };
+ # sector and tower
+ my @where_sector = $class->tower_sector_sql($params);
+ if ( @where_sector ) {
+ push @$where, @where_sector;
+ push @$from, ' LEFT JOIN tower_sector USING ( sectornum )';
+ }
}
}
-
-=item radius_usergroup_selector GROUPS_ARRAYREF [ SELECTNAME ]
-
-=cut
-
-sub radius_usergroup_selector {
- my $sel_groups = shift;
- my %sel_groups = map { $_=>1 } @$sel_groups;
-
- my $selectname = shift || 'radius_usergroup';
-
- my $dbh = dbh;
- my $sth = $dbh->prepare(
- 'SELECT DISTINCT(groupname) FROM radius_usergroup ORDER BY groupname'
- ) or die $dbh->errstr;
- $sth->execute() or die $sth->errstr;
- my @all_groups = map { $_->[0] } @{$sth->fetchall_arrayref};
-
- my $html = <<END;
- <SCRIPT>
- function ${selectname}_doadd(object) {
- var myvalue = object.${selectname}_add.value;
- var optionName = new Option(myvalue,myvalue,false,true);
- var length = object.$selectname.length;
- object.$selectname.options[length] = optionName;
- object.${selectname}_add.value = "";
- }
- </SCRIPT>
- <SELECT MULTIPLE NAME="$selectname">
-END
-
- foreach my $group ( @all_groups ) {
- $html .= qq(<OPTION VALUE="$group");
- if ( $sel_groups{$group} ) {
- $html .= ' SELECTED';
- $sel_groups{$group} = 0;
- }
- $html .= ">$group</OPTION>\n";
- }
- foreach my $group ( grep { $sel_groups{$_} } keys %sel_groups ) {
- $html .= qq(<OPTION VALUE="$group" SELECTED>$group</OPTION>\n);
- };
- $html .= '</SELECT>';
-
- $html .= qq!<BR><INPUT TYPE="text" NAME="${selectname}_add">!.
- qq!<INPUT TYPE="button" VALUE="Add new group" onClick="${selectname}_doadd(this.form)">!;
-
- $html;
-}
-
=item reached_threshold
Performs some activities when svc_acct thresholds (such as number of seconds
current object. This is probably a bug as it's unexpected and
counterintuitive.
-radius_usergroup_selector? putting web ui components in here? they should
-probably live somewhere else...
-
insertion of RADIUS group stuff in insert could be done with child_objects now
(would probably clean up export of them too)
=cut
-=item domain_select_hash %OPTIONS
-
-Returns a hash SVCNUM => DOMAIN ... representing the domains this customer
-may at present purchase.
-
-Currently available options are: I<pkgnum> I<svcpart>
-
-=cut
-
-sub domain_select_hash {
- my ($self, %options) = @_;
- my %domains = ();
- my $part_svc;
- my $cust_pkg;
-
- if (ref($self)) {
- $part_svc = $self->part_svc;
- $cust_pkg = $self->cust_svc->cust_pkg
- if $self->cust_svc;
- }
-
- $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} })
- if $options{'svcpart'};
-
- $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} })
- if $options{'pkgnum'};
-
- if ($part_svc && ( $part_svc->part_svc_column('domsvc')->columnflag eq 'S'
- || $part_svc->part_svc_column('domsvc')->columnflag eq 'F')) {
- %domains = map { $_->svcnum => $_->domain }
- map { qsearchs('svc_domain', { 'svcnum' => $_ }) }
- split(',', $part_svc->part_svc_column('domsvc')->columnvalue);
- }elsif ($cust_pkg && !$conf->exists('svc_acct-alldomains') ) {
- %domains = map { $_->svcnum => $_->domain }
- map { qsearchs('svc_domain', { 'svcnum' => $_->svcnum }) }
- map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
- qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum });
- }else{
- %domains = map { $_->svcnum => $_->domain } qsearch('svc_domain', {} );
- }
-
- if ($part_svc && $part_svc->part_svc_column('domsvc')->columnflag eq 'D') {
- my $svc_domain = qsearchs('svc_domain',
- { 'svcnum' => $part_svc->part_svc_column('domsvc')->columnvalue } );
- if ( $svc_domain ) {
- $domains{$svc_domain->svcnum} = $svc_domain->domain;
- }else{
- warn "unknown svc_domain.svcnum for part_svc_column domsvc: ".
- $part_svc->part_svc_column('domsvc')->columnvalue;
-
- }
- }
-
- (%domains);
-}
-
1;
-