add checkbox to payment_receipt_email config
[freeside.git] / FS / FS / Conf.pm
index 639f06b..41c14ad 100644 (file)
@@ -1,10 +1,15 @@
 package FS::Conf;
 
-use vars qw($default_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%%%';
+
 
 $DEBUG = 0;
 
@@ -16,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');
@@ -42,34 +42,33 @@ 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 } ;
+  my($self) = { 'base_dir' => $base_dir };
   bless ($self, $class);
 }
 
-=item dir
+=item base_dir
 
-Returns the directory.
+Returns the base directory.  By default this is /usr/local/etc/freeside.
 
 =cut
 
-sub dir {
+sub base_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 =~ /^(.*)$/;
+  my $base_dir = $self->{base_dir};
+  -e $base_dir or die "FATAL: $base_dir doesn't exist!";
+  -d $base_dir or die "FATAL: $base_dir isn't a directory!";
+  -r $base_dir or die "FATAL: Can't read $base_dir!";
+  -x $base_dir or die "FATAL: $base_dir not searchable (executable)!";
+  $base_dir =~ /^(.*)$/;
   $1;
 }
 
@@ -79,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];
   }
 }
 
@@ -103,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
@@ -119,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
@@ -132,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);
   }
 }
 
@@ -147,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
@@ -162,23 +162,41 @@ 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 $error;
+  if ($old) {
+    $error = $new->replace($old);
+  } else {
+    $error = $new->insert;
   }
+
+  die "error setting configuration value: $error \n"
+    if $error;
+
+}
+
+=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
 
@@ -187,11 +205,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;
+
   }
 }
 
@@ -207,65 +237,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 <a href="../docs/billing.html">billing documentation</a> 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 <a href="../docs/billing.html">billing documentation</a> 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 <a href="../docs/billing.html">billing documentation</a> 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 <a href="../docs/billing.html">billing documentation</a> for details.',
                            'type'        => 'textarea',
                          }
-      } glob($self->dir. '/invoice_latexnotes_*')
+      } FS::Record::qsearch('conf', {}, '', "WHERE name LIKE 'invoice!_latexnotes!_%' ESCAPE '!'")
   );
 }
 
@@ -473,6 +506,13 @@ httemplate/docs/config.html
   },
 
   {
+    'key'         => 'deleterefunds',
+    'section'     => 'billing',
+    'description' => 'Enable deletion of unclosed refunds.  Be very careful!  Only delete refunds that were data-entry errors, not adjustments.',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'unapplypayments',
     'section'     => 'deprecated',
     'description' => '<B>DEPRECATED</B>, now controlled by ACLs.  Used to enable "unapplication" of unclosed payments.',
@@ -529,6 +569,13 @@ httemplate/docs/config.html
   },
 
   {
+    'key'         => 'emailinvoiceautoalways',
+    'section'     => 'billing',
+    'description' => 'Automatically adds new accounts to the email invoice list even when the list contains email addresses',
+    'type'       => 'checkbox',
+  },
+
+  {
     'key'         => 'exclude_ip_addr',
     'section'     => '',
     'description' => 'Exclude these from the list of available broadband service IP addresses. (One per line)',
@@ -703,7 +750,7 @@ httemplate/docs/config.html
     'key'         => 'payment_receipt_email',
     'section'     => 'billing',
     'description' => 'Template file for payment receipts.  Payment receipts are sent to the customer email invoice destination(s) when a payment is received.  See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language.  The following variables are available: <ul><li><code>$date</code> <li><code>$name</code> <li><code>$paynum</code> - Freeside payment number <li><code>$paid</code> - Amount of payment <li><code>$payby</code> - Payment type (Card, Check, Electronic check, etc.) <li><code>$payinfo</code> - Masked credit card number or check number <li><code>$balance</code> - New balance</ul>',
-    'type'        => 'textarea',
+    'type'        => [qw( checkbox textarea )],
   },
 
   {
@@ -1130,7 +1177,14 @@ httemplate/docs/config.html
   {
     'key'         => 'show_ss',
     'section'     => 'UI',
-    'description' => 'Turns on display/collection of SS# in the web interface.',
+    'description' => 'Turns on display/collection of social security numbers in the web interface.  Sometimes required by electronic check (ACH) processors.',
+    'type'        => 'checkbox',
+  },
+
+  {
+    'key'         => 'show_stateid',
+    'section'     => 'UI',
+    'description' => "Turns on display/collection of driver's license/state issued id numbers in the web interface.  Sometimes required by electronic check (ACH) processors.",
     'type'        => 'checkbox',
   },
 
@@ -1805,26 +1859,6 @@ httemplate/docs/config.html
     'type'        => 'checkbox',
   },
 
-  #these should become per-user...
-  {
-    'key'         => 'vonage-username',
-    'section'     => '',
-    'description' => 'Vonage Click2Call username (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
-    'type'        => 'text',
-  },
-  {
-    'key'         => 'vonage-password',
-    'section'     => '',
-    'description' => 'Vonage Click2Call username (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
-    'type'        => 'text',
-  },
-  {
-    'key'         => 'vonage-fromnumber',
-    'section'     => '',
-    'description' => 'Vonage Click2Call number (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
-    'type'        => 'text',
-  },
-
   {
     'key'         => 'echeck-nonus',
     'section'     => 'billing',
@@ -1856,7 +1890,7 @@ httemplate/docs/config.html
   {
     'key'         => 'batch-enable',
     'section'     => 'billing',
-    'description' => 'Enable credit card batching - leave disabled for real-time installations.',
+    'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.',
     'type'        => 'checkbox',
   },
 
@@ -1865,7 +1899,10 @@ httemplate/docs/config.html
     'section'     => 'billing',
     'description' => 'Default format for batches.',
     'type'        => 'select',
-    'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ]
+    'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch',
+                       'csv-chase_canada-E-xactBatch', 'BoM', 'PAP',
+                       'ach-spiritone',
+                    ]
   },
 
   {
@@ -1873,7 +1910,8 @@ httemplate/docs/config.html
     'section'     => 'billing',
     'description' => 'Fixed (unchangeable) format for credit card batches.',
     'type'        => 'select',
-    'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ]
+    'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ,
+                       'csv-chase_canada-E-xactBatch', 'BoM', 'PAP' ]
   },
 
   {
@@ -1881,7 +1919,16 @@ httemplate/docs/config.html
     'section'     => 'billing',
     'description' => 'Fixed (unchangeable) format for electronic check batches.',
     'type'        => 'select',
-    'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ]
+    'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP',
+                       'ach-spiritone',
+                     ]
+  },
+
+  {
+    'key'         => 'batch-increment_expiration',
+    'section'     => 'billing',
+    'description' => 'Increment expiration date years in batches until cards are current.  Make sure this is acceptable to your batching provider before enabling.',
+    'type'        => 'checkbox'
   },
 
   {
@@ -1892,6 +1939,20 @@ httemplate/docs/config.html
   },
 
   {
+    'key'         => 'batchconfig-PAP',
+    'section'     => 'billing',
+    'description' => 'Configuration for PAP batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
+    'type'        => 'textarea',
+  },
+
+  {
+    'key'         => 'batchconfig-csv-chase_canada-E-xactBatch',
+    'section'     => 'billing',
+    'description' => 'Gateway ID for Chase Canada E-xact batching',
+    'type'        => 'text',
+  },
+
+  {
     'key'         => 'payment_history-years',
     'section'     => 'UI',
     'description' => 'Number of years of payment history to show by default.  Currently defaults to 2.',
@@ -1970,6 +2031,56 @@ httemplate/docs/config.html
     'select_enum' => \@card_types,
   },
 
+  {
+    'key'         => 'dashboard-toplist',
+    'section'     => 'UI',
+    'description' => 'List of items to display on the top of the front page',
+    'type'        => 'textarea',
+  },
+
+  {
+    'key'         => 'impending_recur_template',
+    'section'     => 'billing',
+    'description' => 'Template file for alerts about looming first time recurrant billing.  See the <a href="http://search.cpan.org/~mjd/Text-Template.pm">Text::Template</a> documentation for details on the template substitition language.  Also see packages with a <a href="../browse/part_pkg.cgi">flat price plan</a>  The following variables are available<ul><li><code>$packages</code> allowing <code>$packages->[0]</code> thru <code>$packages->[n]</code> <li><code>$package</code> the first package, same as <code>$packages->[0]</code> <li><code>$recurdates</code> allowing <code>$recurdates->[0]</code> thru <code>$recurdates->[n]</code> <li><code>$recurdate</code> the first recurdate, same as <code>$recurdate->[0]</code> <li><code>$first</code> <li><code>$last</code></ul>',
+# <li><code>$payby</code> <li><code>$expdate</code> most likely only confuse
+    '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'     => '',
+    'description' => 'Ignores service quantity restrictions in self-service context.  Strongly not recommended - just set your quantities correctly in the first place.',
+    'type'        => 'checkbox',
+  },
+
+  {
+    'key'         => 'disable_setup_suspended_pkgs',
+    'section'     => 'billing',
+    'description' => 'Disables charging of setup fees for suspended packages.',
+    'type'       => 'checkbox',
+  },
+
+  {
+    'key' => 'password-generated-allcaps',
+    'section' => 'password',
+    'description' => 'Causes passwords automatically generated to consist entirely of capital letters',
+    'type' => 'checkbox',
+  },
+
 );
 
 1;