From 262ad6a5821649093c104d59b33d838c12520d3e Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 19 Feb 2007 15:40:30 +0000 Subject: this should fix Can't call method "part_pkg" on an undefined value at .../cust_bill.pm line 434 --- FS/FS/cust_bill.pm | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 2c0b35388..844d1b867 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -432,12 +432,16 @@ sub apply_payments_and_credits { my @open_lineitems = $self->open_cust_bill_pkg; my $max_pay_weight = - max( map { $_->cust_pkg->part_pkg->pay_weight || 0 } - @open_lineitems + max( map { $_->part_pkg->pay_weight || 0 } + grep { $_ } + map { $_->cust_pkg } + @open_lineitems ); my $max_credit_weight = - max( map { $_->cust_pkg->part_pkg->credit_weight || 0 } - @open_lineitems + max( map { $_->part_pkg->credit_weight || 0 } + grep { $_ } + map { $_->cust_pkg } + @open_lineitems ); #if both are the same... payments first? it has to be something -- cgit v1.2.1 From 299f1b7b7b5691d70709aaacfd100d2e088ab0ff Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 21 Feb 2007 02:53:14 +0000 Subject: work around a claimed 50 char limit, and correct a description handling bug --- FS/FS/part_export/prizm.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FS/FS/part_export/prizm.pm b/FS/FS/part_export/prizm.pm index 711888d1f..ca02b8f3e 100644 --- a/FS/FS/part_export/prizm.pm +++ b/FS/FS/part_export/prizm.pm @@ -43,7 +43,7 @@ sub _export_insert { my $cust_main = $svc->cust_svc->cust_pkg->cust_main; - my $err_or_som = $self->prizm_command(CustomerIfService, 'getCustomers', + my $err_or_som = $self->prizm_command('CustomerIfService', 'getCustomers', ['import_id'], [$cust_main->custnum], ['='], @@ -118,7 +118,7 @@ sub _export_insert { $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement', $networkid, $svc->mac_addr, - $name . " " . $svc->description, + $name, # we fix this below (bug in prizm?) $location, $contact, sprintf("%032X", $svc->authkey), @@ -141,7 +141,7 @@ sub _export_insert { $svc->latitude, $svc->longitude, $svc->altitude, - $name, + $name . " " . $svc->description, $location, $contact, ); -- cgit v1.2.1 From e608d6097db8e63f52679a5686543b86f6bc1830 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 21 Feb 2007 11:26:02 +0000 Subject: add a _password_encoding field --- FS/FS/Schema.pm | 5 +- FS/FS/part_export/shellcommands.pm | 13 +- FS/FS/svc_acct.pm | 335 ++++++++++++++++++++++++++++--------- 3 files changed, 264 insertions(+), 89 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 0d67834a0..bae7522f8 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -842,8 +842,9 @@ sub tables_hashref { 'svc_acct' => { 'columns' => [ 'svcnum', 'int', '', '', '', '', - 'username', 'varchar', '', $username_len, '', '', #unique (& remove dup code) - '_password', 'varchar', '', 72, '', '', #13 for encryped pw's plus ' *SUSPENDED* (md5 passwords can be 34, blowfish 60) + 'username', 'varchar', '', $username_len, '', '', + '_password', 'varchar', '', 512, '', '', + '_password_encoding', 'varchar', 'NULL', $char_d, '', '', 'sec_phrase', 'varchar', 'NULL', $char_d, '', '', 'popnum', 'int', 'NULL', '', '', '', 'uid', 'int', 'NULL', '', '', '', diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index b43033405..29e0a5799 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -157,12 +157,13 @@ old_ for replace operations):
  • $username
  • $_password
  • $quoted_password - unencrypted password, already quoted for the shell (do not add additional quotes). -
  • $crypt_password - encrypted password. When used on the command line (rather than STDIN), it will be already quoted for the shell (do not add additional quotes). +
  • $crypt_password - encrypted password. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes). +
  • $ldap_password - Password in LDAP/RFC2307 format (for example, "{PLAIN}himom", "{CRYPT}94pAVyK/4oIBk" or "{MD5}5426824942db4253f87a1009fd5d2d4"). When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
  • $uid
  • $gid -
  • $finger - GECOS. When used on the command line (rather than STDIN), it will be already quoted for the shell (do not add additional quotes). -
  • $first - First name of GECOS. When used on the command line (rather than STDIN), it will be already quoted for the shell (do not add additional quotes). -
  • $last - Last name of GECOS. When used on the command line (rather than STDIN), it will be already quoted for the shell (do not add additional quotes). +
  • $finger - GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes). +
  • $first - First name of GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes). +
  • $last - Last name of GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
  • $dir - home directory
  • $shell
  • $quota @@ -249,6 +250,7 @@ sub _export_command { $quoted_password = shell_quote $_password; $crypt_password = $svc_acct->crypt_password( $self->option('crypt') ); + $ldap_password = $svc_acct->ldap_password( $self->option('crypt') ); @radius_groups = $svc_acct->radius_groups; @@ -291,6 +293,7 @@ sub _export_command { $last = shell_quote $last; $finger = shell_quote $finger; $crypt_password = shell_quote $crypt_password; + $ldap_password = shell_quote $ldap_password; my $command_string = eval(qq("$command")); @@ -320,6 +323,7 @@ sub _export_replace { $new_domain = $new->domain; $new_crypt_password = $new->crypt_password( $self->option('crypt') ); + $new_ldap_password = $new->ldap_password( $self->option('crypt') ); @old_radius_groups = $old->radius_groups; @new_radius_groups = $new->radius_groups; @@ -357,6 +361,7 @@ sub _export_replace { $new_last = shell_quote $new_last; $new_finger = shell_quote $new_finger; $new_crypt_password = shell_quote $new_crypt_password; + $new_ldap_password = shell_quote $new_ldap_password; my $command_string = eval(qq("$command")); diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 0a7d6be6d..48116d761 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -21,6 +21,7 @@ use Fcntl qw(:flock); use Date::Format; use Crypt::PasswdMD5 1.2; use Data::Dumper; +use Authen::Passphrase; use FS::UID qw( datasrc ); use FS::Conf; use FS::Record qw( qsearch qsearchs fields dbh dbdef ); @@ -171,6 +172,8 @@ FS::svc_Common. The following fields are currently supported: =item _password - generated if blank +=item _password_encoding - plain, crypt, ldap (or empty for autodetection) + =item sec_phrase - security phrase =item popnum - Point of presence (see L) @@ -883,6 +886,9 @@ sub check { || $self->ut_snumbern('upbytes') || $self->ut_snumbern('downbytes') || $self->ut_snumbern('totalbytes') + || $self->ut_enum( '_password_encoding', + [ '', qw( plain crypt ldap ) ] + ) ; return $error if $error; @@ -914,12 +920,6 @@ sub check { unless ( $username_ampersand ) { $recref->{username} =~ /\&/ and return gettext('illegal_username'); } - if ( $password_noampersand ) { - $recref->{_password} =~ /\&/ and return gettext('illegal_password'); - } - if ( $password_noexclamation ) { - $recref->{_password} =~ /\!/ and return gettext('illegal_password'); - } unless ( $username_percent ) { $recref->{username} =~ /\%/ and return gettext('illegal_username'); } @@ -1027,36 +1027,92 @@ sub check { $self->ut_textn($_); } - #generate a password if it is blank - $recref->{_password} = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) ) - unless ( $recref->{_password} ); - - #if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{4,16})$/ ) { - if ( $recref->{_password} =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) { - $recref->{_password} = $1.$3; - #uncomment this to encrypt password immediately upon entry, or run - #bin/crypt_pw in cron to give new users a window during which their - #password is available to techs, for faxing, etc. (also be aware of - #radius issues!) - #$recref->{password} = $1. - # crypt($3,$saltset[int(rand(64))].$saltset[int(rand(64))] - #; - } elsif ( $recref->{_password} =~ /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/ ) { - $recref->{_password} = $1.$3; - } elsif ( $recref->{_password} eq '*' ) { - $recref->{_password} = '*'; - } elsif ( $recref->{_password} eq '!' ) { - $recref->{_password} = '!'; - } elsif ( $recref->{_password} eq '!!' ) { - $recref->{_password} = '!!'; + if ( $recref->{_password_encoding} eq 'ldap' ) { + + if ( $recref->{_password} =~ /^(\{[\w\-]+\})(!?.{0,64})$/ ) { + $recref->{_password} = uc($1).$2; + } else { + return 'Illegal (ldap-encoded) password: '. $recref->{_password}; + } + + } elsif ( $recref->{_password_encoding} eq 'crypt' ) { + + if ( $recref->{_password} =~ + #/^(\$\w+\$.*|[\w\+\/]{13}|_[\w\+\/]{19}|\*)$/ + /^(!!?)?(\$\w+\$.*|[\w\+\/]{13}|_[\w\+\/]{19}|\*)$/ + ) { + + $recref->{_password} = $1.$2; + + } else { + return 'Illegal (crypt-encoded) password'; + } + + } elsif ( $recref->{_password_encoding} eq 'plain' ) { + + #generate a password if it is blank + $recref->{_password} = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) ) + unless length( $recref->{_password} ); + + if ( $recref->{_password} =~ /^([^\t\n]{$passwordmin,$passwordmax})$/ ) { + $recref->{_password} = $1; + } else { + return gettext('illegal_password'). " $passwordmin-$passwordmax ". + FS::Msgcat::_gettext('illegal_password_characters'). + ": ". $recref->{_password}; + } + + if ( $password_noampersand ) { + $recref->{_password} =~ /\&/ and return gettext('illegal_password'); + } + if ( $password_noexclamation ) { + $recref->{_password} =~ /\!/ and return gettext('illegal_password'); + } + } else { - #return "Illegal password"; - return gettext('illegal_password'). " $passwordmin-$passwordmax ". - FS::Msgcat::_gettext('illegal_password_characters'). - ": ". $recref->{_password}; + + #carp "warning: _password_encoding unspecified\n"; + + #generate a password if it is blank + unless ( length( $recref->{_password} ) ) { + + $recref->{_password} = + join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) ); + $recref->{_password_encoding} = 'plain'; + + } else { + + #if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{4,16})$/ ) { + if ( $recref->{_password} =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) { + $recref->{_password} = $1.$3; + $recref->{_password_encoding} = 'plain'; + } elsif ( $recref->{_password} =~ + /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/ + ) { + $recref->{_password} = $1.$3; + $recref->{_password_encoding} = 'crypt'; + } elsif ( $recref->{_password} eq '*' ) { + $recref->{_password} = '*'; + $recref->{_password_encoding} = 'crypt'; + } elsif ( $recref->{_password} eq '!' ) { + $recref->{_password_encoding} = 'crypt'; + $recref->{_password} = '!'; + } elsif ( $recref->{_password} eq '!!' ) { + $recref->{_password} = '!!'; + $recref->{_password_encoding} = 'crypt'; + } else { + #return "Illegal password"; + return gettext('illegal_password'). " $passwordmin-$passwordmax ". + FS::Msgcat::_gettext('illegal_password_characters'). + ": ". $recref->{_password}; + } + + } + } $self->SUPER::check; + } =item _check_system @@ -1888,23 +1944,42 @@ sub check_password { #self-service and pay up ( my $password = $self->_password ) =~ s/^\*SUSPENDED\* //; - #eventually should check a "password-encoding" field - if ( $password =~ /^(\*|!!?)$/ ) { #no self-service login - return 0; - } elsif ( length($password) < 13 ) { #plaintext - $check_password eq $password; - } elsif ( length($password) == 13 ) { #traditional DES crypt - crypt($check_password, $password) eq $password; - } elsif ( $password =~ /^\$1\$/ ) { #MD5 crypt - unix_md5_crypt($check_password, $password) eq $password; - } elsif ( $password =~ /^\$2a?\$/ ) { #Blowfish - warn "Can't check password: Blowfish encryption not yet supported, svcnum". - $self->svcnum. "\n"; - 0; + if ( $self->_password_encoding eq 'ldap' ) { + + my $auth = from_rfc2307 Authen::Passphrase $self->_password; + return $auth->match($check_password); + + } elsif ( $self->_password_encoding eq 'crypt' ) { + + my $auth = from_crypt Authen::Passphrase $self->_password; + return $auth->match($check_password); + + } elsif ( $self->_password_encoding eq 'plain' ) { + + return $check_password eq $password; + } else { - warn "Can't check password: Unrecognized encryption for svcnum ". - $self->svcnum. "\n"; - 0; + + #XXX this could be replaced with Authen::Passphrase stuff + + if ( $password =~ /^(\*|!!?)$/ ) { #no self-service login + return 0; + } elsif ( length($password) < 13 ) { #plaintext + $check_password eq $password; + } elsif ( length($password) == 13 ) { #traditional DES crypt + crypt($check_password, $password) eq $password; + } elsif ( $password =~ /^\$1\$/ ) { #MD5 crypt + unix_md5_crypt($check_password, $password) eq $password; + } elsif ( $password =~ /^\$2a?\$/ ) { #Blowfish + warn "Can't check password: Blowfish encryption not yet supported, ". + "svcnum ". $self->svcnum. "\n"; + 0; + } else { + warn "Can't check password: Unrecognized encryption for svcnum ". + $self->svcnum. "\n"; + 0; + } + } } @@ -1925,14 +2000,40 @@ database. sub crypt_password { my $self = shift; - #eventually should check a "password-encoding" field - if ( length($self->_password) == 13 - || $self->_password =~ /^\$(1|2a?)\$/ - || $self->_password =~ /^(\*|NP|\*LK\*|!!?)$/ - ) - { - $self->_password; - } else { + + if ( $self->_password_encoding eq 'ldap' ) { + + if ( $self->_password =~ /^\{(PLAIN|CLEARTEXT)\}(.+)$/ ) { + my $plain = $2; + + #XXX this could be replaced with Authen::Passphrase stuff + + my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt'; + if ( $encryption eq 'crypt' ) { + crypt( + $self->_password, + $saltset[int(rand(64))].$saltset[int(rand(64))] + ); + } elsif ( $encryption eq 'md5' ) { + unix_md5_crypt( $self->_password ); + } elsif ( $encryption eq 'blowfish' ) { + croak "unknown encryption method $encryption"; + } else { + croak "unknown encryption method $encryption"; + } + + } elsif ( $self->_password =~ /^\{CRYPT\}(.+)$/ ) { + $1; + } + + } elsif ( $self->_password_encoding eq 'crypt' ) { + + return $self->_password; + + } elsif ( $self->_password_encoding eq 'plain' ) { + + #XXX this could be replaced with Authen::Passphrase stuff + my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt'; if ( $encryption eq 'crypt' ) { crypt( @@ -1946,14 +2047,44 @@ sub crypt_password { } else { croak "unknown encryption method $encryption"; } + + } else { + + if ( length($self->_password) == 13 + || $self->_password =~ /^\$(1|2a?)\$/ + || $self->_password =~ /^(\*|NP|\*LK\*|!!?)$/ + ) + { + $self->_password; + } else { + + #XXX this could be replaced with Authen::Passphrase stuff + + my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt'; + if ( $encryption eq 'crypt' ) { + crypt( + $self->_password, + $saltset[int(rand(64))].$saltset[int(rand(64))] + ); + } elsif ( $encryption eq 'md5' ) { + unix_md5_crypt( $self->_password ); + } elsif ( $encryption eq 'blowfish' ) { + croak "unknown encryption method $encryption"; + } else { + croak "unknown encryption method $encryption"; + } + + } + } + } =item ldap_password [ DEFAULT_ENCRYPTION_TYPE ] Returns an encrypted password in "LDAP" format, with a curly-bracked prefix -describing the format, for example, "{CRYPT}94pAVyK/4oIBk" or -"{PLAIN-MD5}5426824942db4253f87a1009fd5d2d4f". +describing the format, for example, "{PLAIN}himom", "{CRYPT}94pAVyK/4oIBk" or +"{MD5}5426824942db4253f87a1009fd5d2d4". The optional DEFAULT_ENCRYPTION_TYPE is not yet used, but the idea is for it to work the same as the B method. @@ -1963,33 +2094,71 @@ to work the same as the B method. sub ldap_password { my $self = shift; #eventually should check a "password-encoding" field - if ( length($self->_password) == 13 ) { #crypt - return '{CRYPT}'. $self->_password; - } elsif ( $self->_password =~ /^\$1\$(.*)$/ && length($1) == 31 ) { #passwdMD5 - return '{MD5}'. $1; - } elsif ( $self->_password =~ /^\$2a?\$(.*)$/ ) { #Blowfish - die "Blowfish encryption not supported in this context, svcnum ". - $self->svcnum. "\n"; - } elsif ( $self->_password =~ /^(\w{48})$/ ) { #LDAP SSHA - return '{SSHA}'. $1; - } elsif ( $self->_password =~ /^(\w{64})$/ ) { #LDAP NS-MTA-MD5 - return '{NS-MTA-MD5}'. $1; - } else { #plaintext + + if ( $self->_password_encoding eq 'ldap' ) { + + return $self->_password; + + } elsif ( $self->_password_encoding eq 'crypt' ) { + + if ( length($self->_password) == 13 ) { #crypt + return '{CRYPT}'. $self->_password; + } elsif ( $self->_password =~ /^\$1\$(.*)$/ && length($1) == 31 ) { #passwdMD5 + return '{MD5}'. $1; + #} elsif ( $self->_password =~ /^\$2a?\$(.*)$/ ) { #Blowfish + # die "Blowfish encryption not supported in this context, svcnum ". + # $self->svcnum. "\n"; + } else { + warn "encryption method not (yet?) supported in LDAP context"; + return '{CRYPT}*'; #unsupported, should not auth + } + + } elsif ( $self->_password_encoding eq 'plain' ) { + return '{PLAIN}'. $self->_password; - #my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt'; - #if ( $encryption eq 'crypt' ) { - # return '{CRYPT}'. crypt( - # $self->_password, - # $saltset[int(rand(64))].$saltset[int(rand(64))] - # ); - #} elsif ( $encryption eq 'md5' ) { - # unix_md5_crypt( $self->_password ); - #} elsif ( $encryption eq 'blowfish' ) { - # croak "unknown encryption method $encryption"; - #} else { - # croak "unknown encryption method $encryption"; - #} + + #return '{CLEARTEXT}'. $self->_password; #? + + } else { + + if ( length($self->_password) == 13 ) { #crypt + return '{CRYPT}'. $self->_password; + } elsif ( $self->_password =~ /^\$1\$(.*)$/ && length($1) == 31 ) { #passwdMD5 + return '{MD5}'. $1; + } elsif ( $self->_password =~ /^\$2a?\$(.*)$/ ) { #Blowfish + warn "Blowfish encryption not supported in this context, svcnum ". + $self->svcnum. "\n"; + return '{CRYPT}*'; + + #are these two necessary anymore? + } elsif ( $self->_password =~ /^(\w{48})$/ ) { #LDAP SSHA + return '{SSHA}'. $1; + } elsif ( $self->_password =~ /^(\w{64})$/ ) { #LDAP NS-MTA-MD5 + return '{NS-MTA-MD5}'. $1; + + } else { #plaintext + return '{PLAIN}'. $self->_password; + + #return '{CLEARTEXT}'. $self->_password; #? + + #XXX this could be replaced with Authen::Passphrase stuff if it gets used + #my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt'; + #if ( $encryption eq 'crypt' ) { + # return '{CRYPT}'. crypt( + # $self->_password, + # $saltset[int(rand(64))].$saltset[int(rand(64))] + # ); + #} elsif ( $encryption eq 'md5' ) { + # unix_md5_crypt( $self->_password ); + #} elsif ( $encryption eq 'blowfish' ) { + # croak "unknown encryption method $encryption"; + #} else { + # croak "unknown encryption method $encryption"; + #} + } + } + } =item domain_slash_username -- cgit v1.2.1 From 14ebc8eed1ce5930089e8cccb8711daa9279c265 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Feb 2007 07:47:07 +0000 Subject: take the package-def defined action here, like freeside-prepaidd --- FS/FS/Cron/bill.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/Cron/bill.pm b/FS/FS/Cron/bill.pm index fb9e5499d..4d77fd08d 100644 --- a/FS/FS/Cron/bill.pm +++ b/FS/FS/Cron/bill.pm @@ -94,7 +94,8 @@ END } $cust_main->ncancelled_pkgs ) { - my $error = $cust_pkg->suspend; + my $action = $cust_pkg->part_pkg->option('recur_action') || 'suspend'; + my $error = $cust_pkg->$action(); warn "Error suspending package ". $cust_pkg->pkgnum. " for custnum ". $cust_main->custnum. ": $error" -- cgit v1.2.1 From b3fde003816e1c3035af95ab6f58cba807a046e9 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 25 Feb 2007 03:08:10 +0000 Subject: add selfservice-ignore_quantity flag --- FS/FS/Conf.pm | 7 +++++++ FS/bin/freeside-selfservice-server | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 8db0b0c6a..2f01e1f40 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -2045,6 +2045,13 @@ httemplate/docs/config.html 'type' => 'textarea', }, + { + 'key' => 'selfservice-ignore_quantity', + 'section' => '', + 'description' => 'Ignores service quantity restrictions in self-service context. Strongly not recommended - just set your quantities correctly in the first place.', + 'type' => 'checkbox', + }, + ); 1; diff --git a/FS/bin/freeside-selfservice-server b/FS/bin/freeside-selfservice-server index 187bc1469..205f1c3ab 100644 --- a/FS/bin/freeside-selfservice-server +++ b/FS/bin/freeside-selfservice-server @@ -16,8 +16,7 @@ use FS::UID qw(adminsuidsetup forksuidsetup); use FS::ClientAPI; use FS::Conf; -use FS::cust_bill; -use FS::cust_pkg; +use FS::cust_svc; $FREESIDE_LOG = "%%%FREESIDE_LOG%%%"; $FREESIDE_LOCK = "%%%FREESIDE_LOCK%%%"; @@ -58,6 +57,10 @@ logfile("$FREESIDE_LOG/selfservice.$machine.log"); daemonize2(); my $conf = new FS::Conf; +if ( $conf->exists('selfservice-ignore_quantity') ) { + $FS::cust_svc::ignore_quantity = 1; + $FS::cust_svc::ignore_quantity = 1; #now it is used twice. +} my $clientd = "/usr/local/sbin/freeside-selfservice-clientd"; #better name? -- cgit v1.2.1 From 1d719ccff55b1a82208fdf44b6dcd2da96b3c42e Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 26 Feb 2007 17:54:19 +0000 Subject: agent_type selector on new packages --- htetc/handler.pl | 3 +-- httemplate/edit/part_pkg.cgi | 44 +++++++++++++++++------------------- httemplate/edit/process/part_pkg.cgi | 2 +- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/htetc/handler.pl b/htetc/handler.pl index c1ca954e1..164da3702 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -119,8 +119,7 @@ sub handler use Chart::LinesPoints; use Chart::Mountain; use Color::Scheme; - use HTML::Widgets::SelectLayers 0.06; - #use HTML::Widgets::SelectLayers 0.07; # after 1.7.2 + use HTML::Widgets::SelectLayers 0.07; use Locale::Country; use FS; use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name); diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 77822d7e0..ce3e8547f 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -159,23 +159,23 @@ Line-item revenue recognition -%#Reseller information # after 1.7.2 -%#<% ntable("#cccccc", 2) %> -%# -%# <% 'Agent Types' %> -%# -%# <% include( '/elements/select-table.html', -%# 'element_name' => 'agent_type', -%# 'table' => 'agent_type', -%# 'name_col' => 'atype', -%# 'value' => \@agent_type, -%# 'empty_label' => '(none)', -%# 'element_etc' => 'multiple size="10"', -%# ) -%# %> -%# -%# -%# +Reseller information +<% ntable("#cccccc", 2) %> + + <% 'Agent Types' %> + + <% include( '/elements/select-table.html', + 'element_name' => 'agent_type', + 'table' => 'agent_type', + 'name_col' => 'atype', + 'value' => \@agent_type, + 'empty_label' => '(none)', + 'element_etc' => 'multiple size="10"', + ) + %> + + + % % @@ -271,9 +271,7 @@ Line-item revenue recognition %#} else { %# push @fixups, 'taxclass'; #hidden %#} -%my @form_elements = ( 'classnum', 'taxclass' ); -%# copying non-existant elements is probably harmless, but after 1.7.2 -%#my @form_elements = ( 'classnum', 'taxclass', 'agent_type' ); +%my @form_elements = ( 'classnum', 'taxclass', 'agent_type' ); % %my @form_radio = (); %if ( dbdef->table('pkg_svc')->column('primary_svc') ) { @@ -330,7 +328,7 @@ Line-item revenue recognition % ( exists($plandata{$field}) % ? $plandata{$field} % : $href->{$field}{'default'} ). -% qq!" onChange="fchanged(this)">!; #after 1.7.2 +% qq!">!; % } elsif ( $href->{$field}{'type'} eq 'checkbox' ) { % $html .= qq!{$field}{'type'} eq 'select_multiple'; -% $html .= qq! NAME="$field" onChange="fchanged(this)">!; # after 1.7.2 +% $html .= qq! NAME="$field">!; % % if ( $href->{$field}{'select_table'} ) { % foreach my $record ( @@ -385,7 +383,7 @@ Line-item revenue recognition % % $html .= ''; #after 1.7.2 +% '">'; % % $html; % diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index 55e7e05ae..1158222b6 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -55,7 +55,7 @@ % $pkgpart = $new->pkgpart; %} % -%unless (1 || $error) { # after 1.7.2 +%unless ($error) { % my $error = $new->process_m2m( % 'link_table' => 'type_pkgs', % 'target_table' => 'agent_type', -- cgit v1.2.1 From fbfffdabe931d704aae420984058e61dc4196b01 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 27 Feb 2007 01:51:52 +0000 Subject: config goes in database --- FS/FS/Conf.pm | 244 +++++++++++++++++++--------------- FS/FS/Schema.pm | 12 ++ FS/FS/UID.pm | 29 ++-- FS/FS/conf.pm | 114 ++++++++++++++++ FS/FS/cust_bill.pm | 28 +++- FS/FS/cust_main.pm | 2 +- FS/FS/svc_acct.pm | 43 +++--- FS/MANIFEST | 2 + FS/bin/freeside-init-config | 92 +++++++++++++ FS/bin/freeside-setup | 27 +++- FS/bin/freeside-upgrade | 8 +- FS/t/conf.t | 5 + conf/invoice_latex | 4 +- httemplate/config/config-download.cgi | 14 ++ httemplate/config/config-process.cgi | 12 +- httemplate/config/config-view.cgi | 8 ++ httemplate/config/config.cgi | 6 +- 17 files changed, 494 insertions(+), 156 deletions(-) create mode 100644 FS/FS/conf.pm create mode 100755 FS/bin/freeside-init-config create mode 100644 FS/t/conf.t create mode 100644 httemplate/config/config-download.cgi diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 2f01e1f40..594f3b3eb 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1,13 +1,14 @@ package FS::Conf; -use vars qw($default_dir $base_dir @config_items @card_types $DEBUG ); -use IO::File; -use File::Basename; +use vars qw($base_dir @config_items @card_types $DEBUG ); +use MIME::Base64; use FS::ConfItem; use FS::ConfDefaults; +use FS::conf; +use FS::Record qw(qsearch qsearchs); +use FS::UID qw(dbh); $base_dir = '%%%FREESIDE_CONF%%%'; -$default_dir = '%%%FREESIDE_CONF%%%'; $DEBUG = 0; @@ -20,13 +21,8 @@ FS::Conf - Freeside configuration values use FS::Conf; - $conf = new FS::Conf "/config/directory"; - - $FS::Conf::default_dir = "/config/directory"; $conf = new FS::Conf; - $dir = $conf->dir; - $value = $conf->config('key'); @list = $conf->config('key'); $bool = $conf->exists('key'); @@ -46,39 +42,19 @@ but this may change in the future. =over 4 -=item new [ DIRECTORY ] +=item new -Create a new configuration object. A directory arguement is required if -$FS::Conf::default_dir has not been set. +Create a new configuration object. =cut sub new { - my($proto,$dir) = @_; + my($proto) = @_; my($class) = ref($proto) || $proto; - my($self) = { 'dir' => $dir || $default_dir, - 'base_dir' => $base_dir, - }; + my($self) = { 'base_dir' => $base_dir }; bless ($self, $class); } -=item dir - -Returns the conf directory. - -=cut - -sub dir { - my($self) = @_; - my $dir = $self->{dir}; - -e $dir or die "FATAL: $dir doesn't exist!"; - -d $dir or die "FATAL: $dir isn't a directory!"; - -r $dir or die "FATAL: Can't read $dir!"; - -x $dir or die "FATAL: $dir not searchable (executable)!"; - $dir =~ /^(.*)$/; - $1; -} - =item base_dir Returns the base directory. By default this is /usr/local/etc/freeside. @@ -102,20 +78,29 @@ Returns the configuration value or values (depending on context) for key. =cut +sub _config { + my($self,$name,$agent)=@_; + my $hashref = { 'name' => $name }; + if (defined($agent) && $agent) { + $hashref->{agent} = $agent; + } + local $FS::Record::conf = undef; # XXX evil hack prevents recursion + my $cv = FS::Record::qsearchs('conf', $hashref); + if (!$cv && exists($hashref->{agent})) { + delete($hashref->{agent}); + $cv = FS::Record::qsearchs('conf', $hashref); + } + return $cv; +} + sub config { - my($self,$file)=@_; - my($dir)=$self->dir; - my $fh = new IO::File "<$dir/$file" or return; + my($self,$name,$agent)=@_; + my $cv = $self->_config($name, $agent) or return; + if ( wantarray ) { - map { - /^(.*)$/ - or die "Illegal line (array context) in $dir/$file:\n$_\n"; - $1; - } <$fh>; + split "\n", $cv->value; } else { - <$fh> =~ /^(.*)$/ - or die "Illegal line (scalar context) in $dir/$file:\n$_\n"; - $1; + (split("\n", $cv->value))[0]; } } @@ -126,12 +111,9 @@ Returns the exact scalar value for key. =cut sub config_binary { - my($self,$file)=@_; - my($dir)=$self->dir; - my $fh = new IO::File "<$dir/$file" or return; - local $/; - my $content = <$fh>; - $content; + my($self,$name,$agent)=@_; + my $cv = $self->_config($name, $agent) or return; + decode_base64($cv->value); } =item exists KEY @@ -142,9 +124,8 @@ is undefined. =cut sub exists { - my($self,$file)=@_; - my($dir) = $self->dir; - -e "$dir/$file"; + my($self,$name,$agent)=@_; + defined($self->_config($name, $agent)); } =item config_orbase KEY SUFFIX @@ -155,11 +136,11 @@ KEY_SUFFIX, if it exists, otherwise for KEY =cut sub config_orbase { - my( $self, $file, $suffix ) = @_; - if ( $self->exists("${file}_$suffix") ) { - $self->config("${file}_$suffix"); + my( $self, $name, $suffix ) = @_; + if ( $self->exists("${name}_$suffix") ) { + $self->config("${name}_$suffix"); } else { - $self->config($file); + $self->config($name); } } @@ -170,12 +151,8 @@ Creates the specified configuration key if it does not exist. =cut sub touch { - my($self, $file) = @_; - my $dir = $self->dir; - unless ( $self->exists($file) ) { - warn "[FS::Conf] TOUCH $file\n" if $DEBUG; - system('touch', "$dir/$file"); - } + my($self, $name, $agent) = @_; + $self->set($name, '', $agent); } =item set KEY VALUE @@ -185,23 +162,49 @@ Sets the specified configuration key to the given value. =cut sub set { - my($self, $file, $value) = @_; - my $dir = $self->dir; + my($self, $name, $value, $agent) = @_; $value =~ /^(.*)$/s; $value = $1; - unless ( join("\n", @{[ $self->config($file) ]}) eq $value ) { - warn "[FS::Conf] SET $file\n" if $DEBUG; -# warn "$dir" if is_tainted($dir); -# warn "$dir" if is_tainted($file); - chmod 0644, "$dir/$file"; - my $fh = new IO::File ">$dir/$file" or return; - chmod 0644, "$dir/$file"; - print $fh "$value\n"; + + warn "[FS::Conf] SET $file\n" if $DEBUG; + + my $old = FS::Record::qsearchs('conf', {name => $name, agent => $agent}); + my $new = new FS::conf { $old ? $old->hash + : ('name' => $name, 'agent' => $agent) + }; + $new->value($value); + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error; + if ($old) { + $error = $new->replace($old); + }else{ + $error = $new->insert; + } + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + die "error setting configuration value: $error \n" } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + +} + +=item set_binary KEY VALUE + +Sets the specified configuration key to an exact scalar value which +can be retrieved with config_binary. + +=cut + +sub set_binary { + my($self,$name, $value, $agent)=@_; + $self->set($name, encode_base64($value), $agent); } -#sub is_tainted { -# return ! eval { join('',@_), kill 0; 1; }; -# } =item delete KEY @@ -210,11 +213,23 @@ Deletes the specified configuration key. =cut sub delete { - my($self, $file) = @_; - my $dir = $self->dir; - if ( $self->exists($file) ) { + my($self, $name, $agent) = @_; + if ( my $cv = FS::Record::qsearchs('conf', {name => $name, agent => $agent}) ) { warn "[FS::Conf] DELETE $file\n"; - unlink "$dir/$file"; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = $cv->delete; + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + die "error setting configuration value: $error \n" + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + } } @@ -230,65 +245,68 @@ sub config_items { #quelle kludge @config_items, ( map { - my $basename = basename($_); - $basename =~ /^(.*)$/; - $basename = $1; new FS::ConfItem { - 'key' => $basename, + 'key' => $_->name, 'section' => 'billing', 'description' => 'Alternate template file for invoices. See the billing documentation for details.', 'type' => 'textarea', } - } glob($self->dir. '/invoice_template_*') + } FS::Record::qsearch('conf', {}, '', "WHERE name LIKE 'invoice!_template!_%' ESCAPE '!'") + ), + ( map { + new FS::ConfItem { + 'key' => '$_->name', + 'section' => 'billing', #? + 'description' => 'An image to include in some types of invoices', + 'type' => 'binary', + } + } FS::Record::qsearch('conf', {}, '', "WHERE name LIKE 'logo!_%.png' ESCAPE '!'") ), ( map { - my $basename = basename($_); - $basename =~ /^(.*)$/; - $basename = $1; new FS::ConfItem { - 'key' => $basename, + 'key' => $_->name, 'section' => 'billing', 'description' => 'Alternate HTML template for invoices. See the billing documentation for details.', 'type' => 'textarea', } - } glob($self->dir. '/invoice_html_*') + } FS::Record::qsearch('conf', {}, '', "WHERE name LIKE 'invoice!_html!_%' ESCAPE '!'") ), ( map { - my $basename = basename($_); - $basename =~ /^(.*)$/; - $basename = $1; - ($latexname = $basename ) =~ s/latex/html/; + ($latexname = $_->name ) =~ s/latex/html/; new FS::ConfItem { - 'key' => $basename, + 'key' => $_->name, 'section' => 'billing', 'description' => "Alternate Notes section for HTML invoices. Defaults to the same data in $latexname if not specified.", 'type' => 'textarea', } - } glob($self->dir. '/invoice_htmlnotes_*') + } FS::Record::qsearch('conf', {}, '', "WHERE name LIKE 'invoice!_htmlnotes!_%' ESCAPE '!'") ), ( map { - my $basename = basename($_); - $basename =~ /^(.*)$/; - $basename = $1; new FS::ConfItem { - 'key' => $basename, + 'key' => $_->name, 'section' => 'billing', 'description' => 'Alternate LaTeX template for invoices. See the billing documentation for details.', 'type' => 'textarea', } - } glob($self->dir. '/invoice_latex_*') + } FS::Record::qsearch('conf', {}, '', "WHERE name LIKE 'invoice!_latex!_%' ESCAPE '!'") + ), + ( map { + new FS::ConfItem { + 'key' => '$_->name', + 'section' => 'billing', #? + 'description' => 'An image to include in some types of invoices', + 'type' => 'binary', + } + } FS::Record::qsearch('conf', {}, '', "WHERE name LIKE 'logo!_%.eps' ESCAPE '!'") ), ( map { - my $basename = basename($_); - $basename =~ /^(.*)$/; - $basename = $1; new FS::ConfItem { - 'key' => $basename, + 'key' => $_->name, 'section' => 'billing', 'description' => 'Alternate Notes section for LaTeX typeset PostScript invoices. See the billing documentation for details.', 'type' => 'textarea', } - } glob($self->dir. '/invoice_latexnotes_*') + } FS::Record::qsearch('conf', {}, '', "WHERE name LIKE 'invoice!_latexnotes!_%' ESCAPE '!'") ); } @@ -2045,6 +2063,20 @@ httemplate/docs/config.html 'type' => 'textarea', }, + { + 'key' => 'logo.png', + 'section' => 'billing', #? + 'description' => 'An image to include in some types of invoices', + 'type' => 'binary', + }, + + { + 'key' => 'logo.eps', + 'section' => 'billing', #? + 'description' => 'An image to include in some types of invoices', + 'type' => 'binary', + }, + { 'key' => 'selfservice-ignore_quantity', 'section' => '', diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index bae7522f8..d9d5f5a03 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1687,6 +1687,18 @@ sub tables_hashref { 'index' => [], }, + 'conf' => { + 'columns' => [ + 'confnum', 'serial', '', '', '', '', + 'agentnum', 'int', 'NULL', '', '', '', + 'name', 'varchar', '', $char_d, '', '', + 'value', 'varchar', 'NULL', '', '', '', # Pg specific + ], + 'primary_key' => 'confnum', + 'unique' => [ [ 'agentnum', 'name' ]], + 'index' => [], + }, + # name type nullability length default local #'new_table' => { diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm index 8dd928ec7..da573a698 100644 --- a/FS/FS/UID.pm +++ b/FS/FS/UID.pm @@ -4,7 +4,7 @@ use strict; use vars qw( @ISA @EXPORT_OK $cgi $dbh $freeside_uid $user $conf_dir $secrets $datasrc $db_user $db_pass %callback @callback - $driver_name $AutoCommit + $driver_name $AutoCommit $callback_hack ); use subs qw( getsecrets cgisetotaker @@ -12,7 +12,7 @@ use subs qw( use Exporter; use Carp qw(carp croak cluck confess); use DBI; -use FS::Conf; +use IO::File; use FS::CurrentUser; @ISA = qw(Exporter); @@ -24,6 +24,7 @@ $freeside_uid = scalar(getpwnam('freeside')); $conf_dir = "%%%FREESIDE_CONF%%%/"; $AutoCommit = 1; #ours, not DBI +$callback_hack = 0; =head1 NAME @@ -104,12 +105,14 @@ sub forksuidsetup { FS::CurrentUser->load_user($user); - foreach ( keys %callback ) { - &{$callback{$_}}; - # breaks multi-database installs # delete $callback{$_}; #run once - } + unless($callback_hack) { + foreach ( keys %callback ) { + &{$callback{$_}}; + # breaks multi-database installs # delete $callback{$_}; #run once + } - &{$_} foreach @callback; + &{$_} foreach @callback; + } $dbh; } @@ -275,11 +278,11 @@ the `/usr/local/etc/freeside/mapsecrets' file. sub getsecrets { my($setuser) = shift; $user = $setuser if $setuser; - my($conf) = new FS::Conf $conf_dir; - if ( $conf->exists('mapsecrets') ) { + if ( -e "$conf_dir/mapsecrets" ) { die "No user!" unless $user; - my($line) = grep /^\s*($user|\*)\s/, $conf->config('mapsecrets'); + my($line) = grep /^\s*($user|\*)\s/, + map { /^(.*)$/; $1 } readline(new IO::File "$conf_dir/mapsecrets"); confess "User $user not found in mapsecrets!" unless $line; $line =~ /^\s*($user|\*)\s+(.*)$/; $secrets = $2; @@ -289,9 +292,9 @@ sub getsecrets { $secrets = 'secrets'; } - ($datasrc, $db_user, $db_pass) = $conf->config($secrets) - or die "Can't get secrets: $secrets: $!\n"; - $FS::Conf::default_dir = $conf_dir. "/conf.$datasrc"; + ($datasrc, $db_user, $db_pass) = + map { /^(.*)$/; $1 } readline(new IO::File "$conf_dir/$secrets") + or die "Can't get secrets: $secrets: $!\n"; undef $driver_name; ($datasrc, $db_user, $db_pass); } diff --git a/FS/FS/conf.pm b/FS/FS/conf.pm new file mode 100644 index 000000000..6126372cc --- /dev/null +++ b/FS/FS/conf.pm @@ -0,0 +1,114 @@ +package FS::conf; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::conf - Object methods for conf records + +=head1 SYNOPSIS + + use FS::conf; + + $record = new FS::conf \%hash; + $record = new FS::conf { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::conf object represents a configuration value. FS::conf inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item confnum - primary key + +=item agentnum - the agent to which this configuration value applies + +=item name - the name of the configuration value + +=item value - the configuration value + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new configuration value. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +sub table { 'conf'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +=item delete + +Delete this record from the database. + +=cut + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +=item check + +Checks all fields to make sure this is a valid configuration value. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('confnum') + || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum') + || $self->ut_text('name') + || $self->ut_anything('value') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 844d1b867..13174487d 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1827,7 +1827,8 @@ sub print_text { =item print_latex [ TIME [ , TEMPLATE ] ] Internal method - returns a filename of a filled-in LaTeX template for this -invoice (Note: add ".tex" to get the actual filename). +invoice (Note: add ".tex" to get the actual filename), and a filename of +an associated logo (with the .eps extension included). See print_ps and print_pdf for methods that return PostScript and PDF output. @@ -1909,6 +1910,7 @@ sub print_latex { 'quantity' => 1, 'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt', #'notes' => join("\n", $conf->config('invoice_latexnotes') ), + # better hang on to conf_dir for a while 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc", ); @@ -2134,6 +2136,22 @@ sub print_latex { } my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; + my $lh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX', + DIR => $dir, + SUFFIX => '.eps', + UNLINK => 0, + ) or die "can't open temp file: $!\n"; + + if ($template && $conf->exists("logo_${template}.eps")) { + print $lh $conf->config_binary("logo_${template}.eps") + or die "can't write temp file: $!\n"; + }else{ + print $lh $conf->config_binary('logo.eps') + or die "can't write temp file: $!\n"; + } + close $lh; + $invoice_data{'logo_file'} = $lh->filename; + my $fh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX', DIR => $dir, SUFFIX => '.tex', @@ -2149,7 +2167,7 @@ sub print_latex { close $fh; $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename; - return $1; + return ($1, $invoice_data{'logo_file'}); } @@ -2167,7 +2185,7 @@ L and L for conversion functions. sub print_ps { my $self = shift; - my $file = $self->print_latex(@_); + my ($file, $lfile) = $self->print_latex(@_); my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; chdir($dir); @@ -2186,6 +2204,7 @@ sub print_ps { or die "can't open $file.ps: $! (error in LaTeX template?)\n"; unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps", "$file.tex"); + unlink("$lfile"); my $ps = ''; while () { @@ -2212,7 +2231,7 @@ L and L for conversion functions. sub print_pdf { my $self = shift; - my $file = $self->print_latex(@_); + my ($file, $lfile) = $self->print_latex(@_); my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; chdir($dir); @@ -2240,6 +2259,7 @@ sub print_pdf { or die "can't open $file.pdf: $! (error in LaTeX template?)\n"; unlink("$file.dvi", "$file.log", "$file.aux", "$file.pdf", "$file.tex"); + unlink("$lfile"); my $pdf = ''; while () { diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index d775e75d6..fe6aa50a7 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -417,7 +417,7 @@ sub start_copy_skel { #'mg_watchlist_header.watchlist_header_id' => { 'mg_watchlist_details.watchlist_details_id' }, #'mg_user_grid_header.grid_header_id' => { 'mg_user_grid_details.user_grid_details_id' }, #'mg_portfolio_header.portfolio_header_id' => { 'mg_portfolio_trades.portfolio_trades_id' => { 'mg_portfolio_trades_positions.portfolio_trades_positions_id' } }, - my @tables = eval($conf->config_binary('cust_main-skeleton_tables')); + my @tables = eval(join('\n',$conf->config('cust_main-skeleton_tables'))); die $@ if $@; _copy_skel( 'cust_main', #tablename diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 48116d761..a06f4d797 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -8,8 +8,6 @@ use vars qw( @ISA $DEBUG $me $conf $skip_fuzzyfiles $username_noperiod $username_nounderscore $username_nodash $username_uppercase $username_percent $password_noampersand $password_noexclamation - $welcome_template $welcome_from - $welcome_subject $welcome_subject_template $welcome_mimetype $warning_template $warning_from $warning_subject $warning_mimetype $warning_cc $smtpmachine @@ -66,24 +64,6 @@ $FS::UID::callback{'FS::svc_acct'} = sub { $password_noampersand = $conf->exists('password-noexclamation'); $password_noexclamation = $conf->exists('password-noexclamation'); $dirhash = $conf->config('dirhash') || 0; - if ( $conf->exists('welcome_email') ) { - $welcome_template = new Text::Template ( - TYPE => 'ARRAY', - SOURCE => [ map "$_\n", $conf->config('welcome_email') ] - ) or warn "can't create welcome email template: $Text::Template::ERROR"; - $welcome_from = $conf->config('welcome_email-from'); # || 'your-isp-is-dum' - $welcome_subject = $conf->config('welcome_email-subject') || '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') || 'text/plain'; - } else { - $welcome_template = ''; - $welcome_from = ''; - $welcome_subject = ''; - $welcome_mimetype = ''; - } if ( $conf->exists('warning_email') ) { $warning_template = new Text::Template ( TYPE => 'ARRAY', @@ -467,6 +447,7 @@ sub insert { if ( $cust_pkg ) { my $cust_main = $cust_pkg->cust_main; + my $agentnum = $cust_main->agentnum; if ( $conf->exists('emailinvoiceautoalways') || $conf->exists('emailinvoiceauto') @@ -478,7 +459,25 @@ sub insert { } #welcome email - my $to = ''; + 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 ) { @@ -949,7 +948,7 @@ sub check { $recref->{shell} = (grep $_ eq $recref->{shell}, @shells)[0]; } else { return "Illegal shell \`". $self->shell. "\'; ". - $conf->dir. "/shells contains: @shells"; + "shells configuration value contains: @shells"; } } else { $recref->{shell} = '/bin/sync'; diff --git a/FS/MANIFEST b/FS/MANIFEST index 82f106412..597cd366b 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -371,3 +371,5 @@ FS/reason_type.pm t/reason_type.t FS/cust_pkg_option.pm t/cust_pkg_option.t +FS/conf.pm +t/conf.t diff --git a/FS/bin/freeside-init-config b/FS/bin/freeside-init-config new file mode 100755 index 000000000..a186d1a85 --- /dev/null +++ b/FS/bin/freeside-init-config @@ -0,0 +1,92 @@ +#!/usr/bin/perl -Tw + +use strict; +use vars qw($opt_u $opt_f $opt_v); +use Getopt::Std; +use IO::File; +use FS::UID qw(adminsuidsetup checkeuid dbh); +use FS::CurrentUser; +use FS::Record qw(qsearch); + + +die "Not running uid freeside!" unless checkeuid(); + +getopts("u:vf"); +my $dir = shift or die &usage; + +$FS::CurrentUser::upgrade_hack = 1; +adminsuidsetup $opt_u; #$user; + +$|=1; + +my $conf = new FS::Conf; +if (!scalar(qsearch('conf', {})) || $opt_f) { + + foreach my $item ( $conf->config_items() ) { + insert_config_item($item,$dir); + } + + # ugly pseudo false laziness with Conf.pm + foreach my $item ( map { my $basename = basename($_); + $basename =~ /^(.*)$/; + $basename = $1; + new FS::ConfItem { + 'key' => $basename, + 'type' => '', + } + } glob($dir. '/invoice_template_*'), + glob($dir. '/invoice_html_*'), + glob($dir. '/invoice_htmlnotes_*'), + glob($dir. '/invoice_latex_*'), + glob($dir. '/invoice_latexnotes_*') + ) { + + insert_config_item($item,$dir); + + } + + foreach my $item ( map { my $basename = basename($_); + $basename =~ /^(.*)$/; + $basename = $1; + new FS::ConfItem { + 'key' => $basename, + 'type' => 'binary', + } + } glob($dir. '/logo_*.png'), + glob($dir. '/logo_*.eps') + ) { + + insert_config_item($item,$dir); + + } + +} + +warn "Freeside database initialized - committing transaction\n" if $opt_v; + +dbh->commit or die dbh->errstr; +dbh->disconnect or die dbh->errstr; + +warn "Configuration initialization committed successfully\n" if $opt_v; + +sub insert_config_item { + local $/; + my ($item,$dir) = @_; + my $key = $item->key; + if (-e "$dir/$key") { + warn "Inserting $key\n" if $opt_v; + my $value = readline(new IO::File "$dir/$key"); + if ($item->type eq 'binary'){ + $conf->set_binary($key, $value); + }else{ + $conf->set($key, $value); + } + } +} + +sub usage { + die "Usage:\n freeside-init-config directory [ -v ] [ -f ]\n" + # [ -u user ] for devel/multi-db installs +} + +1; diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index ddc210f50..bce5a0aeb 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -19,12 +19,15 @@ die "Not running uid freeside!" unless checkeuid(); # map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib; getopts("u:vd:"); -#my $user = shift or die &usage; +my $config_dir = shift || 'conf' ; +$config_dir =~ /^([\w.:=]+)$/ + or die "unacceptable configuration directory name"; +$config_dir = $1; -getsecrets($opt_u); #$user); +getsecrets($opt_u); #needs to match FS::Record -my($dbdef_file) = "%%%FREESIDE_CONF%%%/dbdef.". datasrc; +my($dbdef_file) = "/usr/local/etc/newtest/dbdef.". datasrc; ### @@ -88,7 +91,9 @@ $dbdef->save($dbdef_file); ### $FS::CurrentUser::upgrade_hack = 1; +$FS::UID::callback_hack = 1; my $dbh = adminsuidsetup $opt_u; #$user; +$FS::UID::callback_hack = 0; #create tables $|=1; @@ -105,6 +110,20 @@ dbdef_create($dbh, $dbdef_file); delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload reload_dbdef($dbdef_file); +warn "Freeside schema initialized - commiting transaction\n" if $opt_v; + +$dbh->commit or die $dbh->errstr; +$dbh->disconnect or die $dbh->errstr; + +warn "Database schema committed successfully\n" if $opt_v; + +my $init_config = "freeside-init-config"; +$init_config .= " -v" if $opt_v; +$init_config .= " -u $opt_u" if $opt_u; +$init_config .= " $config_dir"; +system "$init_config" ; + +$dbh = adminsuidsetup $opt_u; create_initial_data('domain' => $opt_d); warn "Freeside database initialized - commiting transaction\n" if $opt_v; @@ -121,7 +140,7 @@ sub dbdef_create { # reverse engineer the schema from the DB and save to file } sub usage { - die "Usage:\n freeside-setup -d domain.name [ -v ]\n" + die "Usage:\n freeside-setup -d domain.name [ -v ] [ config/dir ]\n" # [ -u user ] for devel/multi-db installs } diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade index 3a4e4f8e3..5c646fec2 100755 --- a/FS/bin/freeside-upgrade +++ b/FS/bin/freeside-upgrade @@ -46,6 +46,12 @@ dbdef_create($dbh, $dbdef_file); $dbh->disconnect or die $dbh->errstr; +unless ( $DRY_RUN ) { + my $init_config = "freeside-init-config -u $user "; + $init_config .= "%%%FREESIDE_CONF%%%/conf.". datasrc; + system "$init_config" ; +} + ### sub dbdef_create { # reverse engineer the schema from the DB and save to file @@ -64,7 +70,7 @@ freeside-upgrade - Upgrades database schema for new freeside verisons. =head1 SYNOPSIS - freeside-adduser [ -d ] [ -q | -v ] + freeside-upgrade [ -d ] [ -q | -v ] =head1 DESCRIPTION diff --git a/FS/t/conf.t b/FS/t/conf.t new file mode 100644 index 000000000..5e52079f6 --- /dev/null +++ b/FS/t/conf.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::conf; +$loaded=1; +print "ok 1\n"; diff --git a/conf/invoice_latex b/conf/invoice_latex index d1b471a4b..cf557f4e4 100644 --- a/conf/invoice_latex +++ b/conf/invoice_latex @@ -86,7 +86,7 @@ \returninset \makebox{ \begin{tabular}{ll} - \includegraphics{[@-- $conf_dir --@]/logo.eps} & + \includegraphics{[@-- $logo_file --@]} & \begin{minipage}[b]{5.5cm} [@-- $returnaddress --@] \end{minipage} @@ -94,7 +94,7 @@ } } { % ... pages - %\includegraphics{[@-- $conf_dir --@]/logo.eps} % Uncomment if you want the logo on all pages. + %\includegraphics{[@-- $logo_file --@]} % Uncomment if you want the logo on all pages. } } diff --git a/httemplate/config/config-download.cgi b/httemplate/config/config-download.cgi new file mode 100644 index 000000000..d4b88ded9 --- /dev/null +++ b/httemplate/config/config-download.cgi @@ -0,0 +1,14 @@ +% +% +%my $conf=new FS::Conf; +% +%http_header('Content-Type' => 'application/x-unknown' ); +% +%die "No configuration variable specified (bad URL)!" # umm +% unless $cgi->keywords; +%my($query) = $cgi->keywords; +%$query =~ /^([\w -\)+-\/@;:?=[\]]+)$/; +%my $name = $1; +% +%http_header('Content-Disposition' => "attachment; filename=$name" ); +% print $conf->config_binary($name); diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi index d8f0d8e93..3e49b4f99 100644 --- a/httemplate/config/config-process.cgi +++ b/httemplate/config/config-process.cgi @@ -1,5 +1,4 @@ <%init> - die "access denied\n" unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); @@ -28,6 +27,16 @@ foreach my $i ( @config_items ) { } else { $conf->delete($i->key); } + } elsif ( $type eq 'binary' ) { + if ( defined($cgi->param($i->key. $n)) && $cgi->param($i->key. $n) ) { + my $fh = $cgi->upload($i->key. $n); + if (defined($fh)) { + local $/; + $conf->set_binary($i->key, <$fh>); + } + }else{ + warn "Condition failed for " . $i->key; + } } elsif ( $type eq 'checkbox' ) { # if ( defined($cgi->param($i->key. $n)) && $cgi->param($i->key. $n) ) { if ( defined $cgi->param($i->key. $n) ) { @@ -57,6 +66,5 @@ foreach my $i ( @config_items ) { $conf->touch($_) foreach @touch; $conf->delete($_) foreach @delete; } - <% $cgi->redirect("config-view.cgi") %> diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi index 91ba33769..7f2a1b293 100644 --- a/httemplate/config/config-view.cgi +++ b/httemplate/config/config-view.cgi @@ -42,6 +42,14 @@ no type +% } elsif ( $type eq 'binary' ) { + + + <% $conf->exists($i->key) + ? qq!download! + : 'empty' + %> + % } elsif ( $type eq 'textarea' % || $type eq 'editlist' % || $type eq 'selectmultiple' ) { diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi index 6c3a51aca..df9af47a6 100644 --- a/httemplate/config/config.cgi +++ b/httemplate/config/config.cgi @@ -21,7 +21,7 @@ function SafeOnsubmit() { % my $conf = new FS::Conf; my @config_items = $conf->config_items; -
    + % foreach my $section ( qw(required billing username password UI session % shell BIND % ), @@ -61,6 +61,10 @@ function SafeOnsubmit() { no type +% } elsif ( $type eq 'binary' ) { + + + Filename % } elsif ( $type eq 'textarea' ) { -- cgit v1.2.1 From 6435a77782ea506c5c140136dac1759a4e9747e3 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 27 Feb 2007 21:24:45 +0000 Subject: oops - inappropriate localization was committed --- FS/bin/freeside-setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index bce5a0aeb..ed737b395 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -27,7 +27,7 @@ $config_dir = $1; getsecrets($opt_u); #needs to match FS::Record -my($dbdef_file) = "/usr/local/etc/newtest/dbdef.". datasrc; +my($dbdef_file) = "%%%FREESIDE_CONF%%%/dbdef.". datasrc; ### -- cgit v1.2.1 From 27216c115fc2a2f8b76abc91be9bba599c6516fc Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 27 Feb 2007 22:10:25 +0000 Subject: put the C in ACL here, too --- httemplate/config/config-download.cgi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/httemplate/config/config-download.cgi b/httemplate/config/config-download.cgi index d4b88ded9..95a172a51 100644 --- a/httemplate/config/config-download.cgi +++ b/httemplate/config/config-download.cgi @@ -12,3 +12,7 @@ % %http_header('Content-Disposition' => "attachment; filename=$name" ); % print $conf->config_binary($name); +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + -- cgit v1.2.1 From 393a43193de1a7ab641c4416ef353258c7fd9cf5 Mon Sep 17 00:00:00 2001 From: jayce Date: Tue, 27 Feb 2007 23:59:32 +0000 Subject: adaptation of flat_delayed to work with base_rate billing --- FS/FS/part_pkg/base_delayed.pm | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 FS/FS/part_pkg/base_delayed.pm diff --git a/FS/FS/part_pkg/base_delayed.pm b/FS/FS/part_pkg/base_delayed.pm new file mode 100644 index 000000000..1406a5635 --- /dev/null +++ b/FS/FS/part_pkg/base_delayed.pm @@ -0,0 +1,51 @@ +package FS::part_pkg::flat_delayed; + +use strict; +use vars qw(@ISA %info); +#use FS::Record qw(qsearch qsearchs); +use FS::part_pkg::base_rate; + +@ISA = qw(FS::part_pkg::base_rate); + +%info = ( + 'name' => 'Free (or setup fee) for X days, then base rate'. + ' (anniversary billing)', + 'fields' => { + 'setup_fee' => { 'name' => 'Setup fee for this package', + 'default' => 0, + }, + 'free_days' => { 'name' => 'Initial free days', + 'default' => 0, + }, + 'recur_fee' => { 'name' => 'Recurring base fee for this package', + 'default' => 0, + }, + 'recur_notify' => { 'name' => 'Number of days before recurring billing'. + 'commences to notify customer. (0 means '. + 'no warning)', + 'default' => 0, + }, + 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'. + ' of service at cancellation', + 'type' => 'checkbox', + }, + }, + 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'recur_notify', + 'unused_credit' + ], + #'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value', + #'recur' => 'what.recur_fee.value', + 'weight' => 50, +); + +sub calc_setup { + my($self, $cust_pkg, $time ) = @_; + + my $d = $cust_pkg->bill || $time; + $d += 86400 * $self->option('free_days'); + $cust_pkg->bill($d); + + $self->option('setup_fee'); +} + +1; -- cgit v1.2.1 From 03e436e3fe437a4d8e5516068ef50f2544aa4287 Mon Sep 17 00:00:00 2001 From: jayce Date: Wed, 28 Feb 2007 00:16:38 +0000 Subject: Added base_rate files --- FS/MANIFEST | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FS/MANIFEST b/FS/MANIFEST index 597cd366b..6a4c1ce75 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -126,6 +126,8 @@ FS/part_pkg/sqlradacct_hour.pm FS/part_pkg/subscription.pm FS/part_pkg/voip_sqlradacct.pm FS/part_pkg/voip_cdr.pm +FS/part_pkg/base_rate.pm +FS/part_pkg/base_delayed.pm FS/part_pop_local.pm FS/part_referral.pm FS/part_svc.pm -- cgit v1.2.1 From b7edb246e8b697f12c2b586c5bffef6a2df7ba64 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 28 Feb 2007 19:14:42 +0000 Subject: restore (none) choice to first package select --- httemplate/edit/cust_main/select-domain.html | 1 + httemplate/misc/svc_acct-domains.cgi | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/httemplate/edit/cust_main/select-domain.html b/httemplate/edit/cust_main/select-domain.html index 3d42eb8b1..bec1e834c 100644 --- a/httemplate/edit/cust_main/select-domain.html +++ b/httemplate/edit/cust_main/select-domain.html @@ -42,6 +42,7 @@