import torrus 1.0.9
[freeside.git] / FS / FS / svc_acct.pm
index 5093841..ac336b8 100644 (file)
@@ -1,13 +1,15 @@
 package FS::svc_acct;
 
 use strict;
-use base qw( FS::svc_Domain_Mixin FS::svc_Common );
+use base qw( FS::svc_Domain_Mixin FS::svc_CGP_Mixin FS::svc_CGPRule_Mixin
+             FS::svc_Common );
 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
              $password_noampersand $password_noexclamation
              $warning_template $warning_from $warning_subject $warning_mimetype
              $warning_cc
@@ -46,6 +48,7 @@ use FS::part_export;
 use FS::svc_forward;
 use FS::svc_www;
 use FS::cdr;
+use FS::acct_snarf;
 
 $DEBUG = 0;
 $me = '[FS::svc_acct]';
@@ -72,6 +75,8 @@ FS::UID->install_callback( sub {
   $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');
   $password_noampersand = $conf->exists('password-noexclamation');
   $password_noexclamation = $conf->exists('password-noexclamation');
   $dirhash = $conf->config('dirhash') || 0;
@@ -99,7 +104,7 @@ FS::UID->install_callback( sub {
 );
 
 @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;
@@ -439,7 +444,28 @@ sub table_info {
         'cgp_addmailtrailer' => { label => 'Add trailer to sent mail',
                                   type  => 'checkbox',
                                 },
-        #XXX archive messages, mailing lists
+        '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' => { 
@@ -450,8 +476,9 @@ sub table_info {
                               disable_select    => 1,
                             },
         'cgp_emptytrash' => { 
-                              label => 'Communigate on logout remove trash',
-                              type  => 'text',
+                              label     => 'Communigate on logout remove trash',
+                              type        => 'select',
+                              select_list => __PACKAGE__->cgp_emptytrash_values,
                               disable_inventory => 1,
                               disable_select    => 1,
                             },
@@ -463,71 +490,9 @@ sub table_info {
                             disable_select    => 1,
                           },
         'cgp_timezone' => {
-                            label => 'Communigate time zone',
-                            type  => 'select',
-                            select_list => [ '',
-                                             'HostOS',
-                                             '(+0100) Algeria/Congo',
-                                             '(+0200) Egypt/South Africa',
-                                             '(+0300) Saudi Arabia',
-                                             '(+0400) Oman',
-                                             '(+0500) Pakistan',
-                                             '(+0600) Bangladesh',
-                                             '(+0700) Thailand/Vietnam',
-                                             '(+0800) China/Malaysia',
-                                             '(+0900) Japan/Korea',
-                                             '(+1000) Queensland',
-                                             '(+1100) Micronesia',
-                                             '(+1200) Fiji',
-                                             '(+1300) Tonga/Kiribati',
-                                             '(+1400) Christmas Islands',
-                                             '(-0100) Azores/Cape Verde',
-                                             '(-0200) Fernando de Noronha',
-                                             '(-0300) Argentina/Uruguay',
-                                             '(-0400) Venezuela/Guyana',
-                                             '(-0500) Haiti/Peru',
-                                             '(-0600) Central America',
-                                             '(-0700) Arisona',
-                                             '(-0800) Adamstown',
-                                             '(-0900) Marquesas Islands',
-                                             '(-1000) Hawaii/Tahiti',
-                                             '(-1100) Samoa',
-                                             'Asia/Afghanistan',
-                                             'Asia/India',
-                                             'Asia/Iran',
-                                             'Asia/Iraq',
-                                             'Asia/Israel',
-                                             'Asia/Jordan',
-                                             'Asia/Lebanon',
-                                             'Asia/Syria',
-                                             'Australia/Adelaide',
-                                             'Australia/East',
-                                             'Australia/NorthernTerritory',
-                                             'Europe/Central',
-                                             'Europe/Eastern',
-                                             'Europe/Moscow',
-                                             'Europe/Western',
-                                             'GMT (+0000)',
-                                             'Newfoundland',
-                                             'NewZealand/Auckland',
-                                             'NorthAmerica/Alaska',
-                                             'NorthAmerica/Atlantic',
-                                             'NorthAmerica/Central',
-                                             'NorthAmerica/Eastern',
-                                             'NorthAmerica/Mountain',
-                                             'NorthAmerica/Pacific',
-                                             'Russia/Ekaterinburg',
-                                             'Russia/Irkutsk',
-                                             'Russia/Kamchatka',
-                                             'Russia/Krasnoyarsk',
-                                             'Russia/Magadan',
-                                             'Russia/Novosibirsk',
-                                             'Russia/Vladivostok',
-                                             'Russia/Yakutsk',
-                                             'SouthAmerica/Brasil',
-                                             'SouthAmerica/Chile',
-                                             'SouthAmerica/Paraguay',
-                                           ],
+                            label       => 'Communigate time zone',
+                            type        => 'select',
+                            select_list => __PACKAGE__->cgp_timezone_values,
                             disable_inventory => 1,
                             disable_select    => 1,
                           },
@@ -554,7 +519,6 @@ sub table_info {
         },
 
         #mail
-        #XXX vacation message, redirect all mail, mail rules
         #XXX RPOP settings
 
     },
@@ -781,82 +745,90 @@ sub insert {
     }
 
     #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';
+    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);
     }
-    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";
-        }
+    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 ( $options{'depend_jobnum'} ) {
+            warn "$me depend_jobnum found; adding to welcome email dependancies"
               if $DEBUG;
-            push @jobnums, $options{'depend_jobnum'};
+            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";
+          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 ( $cust_pkg )
+      } # if $welcome_template
+    } # if !$msgnum
+  } # if $cust_pkg
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   ''; #no error
@@ -1228,27 +1200,33 @@ sub check {
               || $self->ut_snumbern('upbytes')
               || $self->ut_snumbern('downbytes')
               || $self->ut_snumbern('totalbytes')
+              || $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
+              # settings
               || $self->ut_alphasn('cgp_rulesallowed')
               || $self->ut_enum('cgp_rpopallowed', [ '', 'Y' ])
               || $self->ut_enum('cgp_mailtoall', [ '', 'Y' ])
               || $self->ut_enum('cgp_addmailtrailer', [ '', 'Y' ])
-              #preferences
+              || $self->ut_snumbern('cgp_archiveafter')
+              # preferences
               || $self->ut_alphasn('cgp_deletemode')
-              || $self->ut_alphan('cgp_emptytrash')
+              || $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')
-              #XXX vacation message, redirect all mail, mail rules
-              #XXX RPOP settings
   ;
   return $error if $error;
 
@@ -1268,16 +1246,14 @@ sub check {
   }
 
   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;
+
+  unless ( $username_uppercase ) {
+    $recref->{username} =~ /[A-Z]/ and return gettext('illegal_username');
+  }
   if ( $username_letterfirst ) {
     $recref->{username} =~ /^[a-z]/ or return gettext('illegal_username');
   } elsif ( $username_letter ) {
@@ -1301,6 +1277,12 @@ sub check {
   unless ( $username_colon ) {
     $recref->{username} =~ /\:/ and return gettext('illegal_username');
   }
+  unless ( $username_slash ) {
+    $recref->{username} =~ /\// and return gettext('illegal_username');
+  }
+  unless ( $username_equals ) {
+    $recref->{username} =~ /\=/ and return gettext('illegal_username');
+  }
 
   $recref->{popnum} =~ /^(\d*)$/ or return "Illegal popnum: ".$recref->{popnum};
   $recref->{popnum} = $1;
@@ -1380,7 +1362,7 @@ sub check {
     }
   }
   $self->getfield('finger') =~
-    /^([\w \t\!\@\#\$\%\&\(\)\-\+\;\'\"\,\.\?\/\*\<\>]*)$/
+    /^([µ_0123456789aAáÁàÀâÂåÅäÄãêæÆbBcCçÇdDðÐeEéÉèÈêÊëËfFgGhHiIíÍìÌîÎïÏjJkKlLmMnNñÑoOóÓòÒôÔöÖõÕøغpPqQrRsSßtTuUúÚùÙûÛüÜvVwWxXyYýÝÿzZþÞ \t\!\@\#\$\%\&\(\)\-\+\;\'\"\,\.\?\/\*\<\>]*)$/
       or return "Illegal finger: ". $self->getfield('finger');
   $self->setfield('finger', $1);
 
@@ -1958,17 +1940,27 @@ sub email {
 =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 {
   my $self = shift;
-  return () unless dbdef->table('acct_snarf');
-  eval "use FS::acct_snarf;";
-  die $@ if $@;
-  qsearch('acct_snarf', { 'svcnum' => $self->svcnum } );
+  qsearch({
+    'table'    => 'acct_snarf',
+    'hashref'  => { 'svcnum' => $self->svcnum },
+    #'order_by' => 'ORDER BY priority ASC',
+  });
+}
+
+=item cgp_rpop_hashref
+
+Returns an arrayref of RPOP data suitable for Communigate Pro API commands.
+
+=cut
+
+sub cgp_rpop_hashref {
+  my $self = shift;
+  { map { $_->snarfname => $_->cgp_hashref } $self->acct_snarf };
 }
 
 =item decrement_upbytes OCTETS
@@ -2283,7 +2275,7 @@ sub set_usage {
   my $reset = 0;
   my %handyhash = ();
   if ( $options{null} ) { 
-    %handyhash = ( map { ( $_ => 'NULL', $_."_threshold" => 'NULL' ) }
+    %handyhash = ( map { ( $_ => undef, $_."_threshold" => undef ) }
                    qw( seconds upbytes downbytes totalbytes )
                  );
   }
@@ -2305,7 +2297,7 @@ sub set_usage {
   #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"
@@ -2314,7 +2306,7 @@ sub set_usage {
   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