Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / FS / FS / Record.pm
1 package FS::Record;
2
3 use strict;
4 use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG
5              %virtual_fields_cache
6              $conf $conf_encryption $money_char $lat_lower $lon_upper
7              $me
8              $nowarn_identical $nowarn_classload
9              $no_update_diff $no_check_foreign
10              @encrypt_payby
11            );
12 use Exporter;
13 use Carp qw(carp cluck croak confess);
14 use Scalar::Util qw( blessed );
15 use File::CounterFile;
16 use Locale::Country;
17 use Text::CSV_XS;
18 use File::Slurp qw( slurp );
19 use DBI qw(:sql_types);
20 use DBIx::DBSchema 0.38;
21 use FS::UID qw(dbh getotaker datasrc driver_name);
22 use FS::CurrentUser;
23 use FS::Schema qw(dbdef);
24 use FS::SearchCache;
25 use FS::Msgcat qw(gettext);
26 use NetAddr::IP; # for validation
27 use Data::Dumper;
28 #use FS::Conf; #dependency loop bs, in install_callback below instead
29
30 use FS::part_virtual_field;
31
32 use Tie::IxHash;
33
34 @ISA = qw(Exporter);
35
36 @encrypt_payby = qw( CARD DCRD CHEK DCHK );
37
38 #export dbdef for now... everything else expects to find it here
39 @EXPORT_OK = qw(
40   dbh fields hfields qsearch qsearchs dbdef jsearch
41   str2time_sql str2time_sql_closing regexp_sql not_regexp_sql concat_sql
42   midnight_sql
43 );
44
45 $DEBUG = 0;
46 $me = '[FS::Record]';
47
48 $nowarn_identical = 0;
49 $nowarn_classload = 0;
50 $no_update_diff = 0;
51 $no_check_foreign = 0;
52
53 my $rsa_module;
54 my $rsa_loaded;
55 my $rsa_encrypt;
56 my $rsa_decrypt;
57
58 $conf = '';
59 $conf_encryption = '';
60 FS::UID->install_callback( sub {
61
62   eval "use FS::Conf;";
63   die $@ if $@;
64   $conf = FS::Conf->new; 
65   $conf_encryption = $conf->exists('encryption');
66   $money_char = $conf->config('money_char') || '$';
67   my $nw_coords = $conf->exists('geocode-require_nw_coordinates');
68   $lat_lower = $nw_coords ? 1 : -90;
69   $lon_upper = $nw_coords ? -1 : 180;
70
71   $File::CounterFile::DEFAULT_DIR = $conf->base_dir . "/counters.". datasrc;
72
73   if ( driver_name eq 'Pg' ) {
74     eval "use DBD::Pg ':pg_types'";
75     die $@ if $@;
76   } else {
77     eval "sub PG_BYTEA { die 'guru meditation #9: calling PG_BYTEA when not running Pg?'; }";
78   }
79
80 } );
81
82 =head1 NAME
83
84 FS::Record - Database record objects
85
86 =head1 SYNOPSIS
87
88     use FS::Record;
89     use FS::Record qw(dbh fields qsearch qsearchs);
90
91     $record = new FS::Record 'table', \%hash;
92     $record = new FS::Record 'table', { 'column' => 'value', ... };
93
94     $record  = qsearchs FS::Record 'table', \%hash;
95     $record  = qsearchs FS::Record 'table', { 'column' => 'value', ... };
96     @records = qsearch  FS::Record 'table', \%hash; 
97     @records = qsearch  FS::Record 'table', { 'column' => 'value', ... };
98
99     $table = $record->table;
100     $dbdef_table = $record->dbdef_table;
101
102     $value = $record->get('column');
103     $value = $record->getfield('column');
104     $value = $record->column;
105
106     $record->set( 'column' => 'value' );
107     $record->setfield( 'column' => 'value' );
108     $record->column('value');
109
110     %hash = $record->hash;
111
112     $hashref = $record->hashref;
113
114     $error = $record->insert;
115
116     $error = $record->delete;
117
118     $error = $new_record->replace($old_record);
119
120     # external use deprecated - handled by the database (at least for Pg, mysql)
121     $value = $record->unique('column');
122
123     $error = $record->ut_float('column');
124     $error = $record->ut_floatn('column');
125     $error = $record->ut_number('column');
126     $error = $record->ut_numbern('column');
127     $error = $record->ut_snumber('column');
128     $error = $record->ut_snumbern('column');
129     $error = $record->ut_money('column');
130     $error = $record->ut_text('column');
131     $error = $record->ut_textn('column');
132     $error = $record->ut_alpha('column');
133     $error = $record->ut_alphan('column');
134     $error = $record->ut_phonen('column');
135     $error = $record->ut_anything('column');
136     $error = $record->ut_name('column');
137
138     $quoted_value = _quote($value,'table','field');
139
140     #deprecated
141     $fields = hfields('table');
142     if ( $fields->{Field} ) { # etc.
143
144     @fields = fields 'table'; #as a subroutine
145     @fields = $record->fields; #as a method call
146
147
148 =head1 DESCRIPTION
149
150 (Mostly) object-oriented interface to database records.  Records are currently
151 implemented on top of DBI.  FS::Record is intended as a base class for
152 table-specific classes to inherit from, i.e. FS::cust_main.
153
154 =head1 CONSTRUCTORS
155
156 =over 4
157
158 =item new [ TABLE, ] HASHREF
159
160 Creates a new record.  It doesn't store it in the database, though.  See
161 L<"insert"> for that.
162
163 Note that the object stores this hash reference, not a distinct copy of the
164 hash it points to.  You can ask the object for a copy with the I<hash> 
165 method.
166
167 TABLE can only be omitted when a dervived class overrides the table method.
168
169 =cut
170
171 sub new { 
172   my $proto = shift;
173   my $class = ref($proto) || $proto;
174   my $self = {};
175   bless ($self, $class);
176
177   unless ( defined ( $self->table ) ) {
178     $self->{'Table'} = shift;
179     carp "warning: FS::Record::new called with table name ". $self->{'Table'}
180       unless $nowarn_classload;
181   }
182   
183   $self->{'Hash'} = shift;
184
185   foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) { 
186     $self->{'Hash'}{$field}='';
187   }
188
189   $self->_rebless if $self->can('_rebless');
190
191   $self->{'modified'} = 0;
192
193   $self->_cache($self->{'Hash'}, shift) if $self->can('_cache') && @_;
194
195   $self;
196 }
197
198 sub new_or_cached {
199   my $proto = shift;
200   my $class = ref($proto) || $proto;
201   my $self = {};
202   bless ($self, $class);
203
204   $self->{'Table'} = shift unless defined ( $self->table );
205
206   my $hashref = $self->{'Hash'} = shift;
207   my $cache = shift;
208   if ( defined( $cache->cache->{$hashref->{$cache->key}} ) ) {
209     my $obj = $cache->cache->{$hashref->{$cache->key}};
210     $obj->_cache($hashref, $cache) if $obj->can('_cache');
211     $obj;
212   } else {
213     $cache->cache->{$hashref->{$cache->key}} = $self->new($hashref, $cache);
214   }
215
216 }
217
218 sub create {
219   my $proto = shift;
220   my $class = ref($proto) || $proto;
221   my $self = {};
222   bless ($self, $class);
223   if ( defined $self->table ) {
224     cluck "create constructor is deprecated, use new!";
225     $self->new(@_);
226   } else {
227     croak "FS::Record::create called (not from a subclass)!";
228   }
229 }
230
231 =item qsearch PARAMS_HASHREF | TABLE, HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ, ADDL_FROM
232
233 Searches the database for all records matching (at least) the key/value pairs
234 in HASHREF.  Returns all the records found as `FS::TABLE' objects if that
235 module is loaded (i.e. via `use FS::cust_main;'), otherwise returns FS::Record
236 objects.
237
238 The preferred usage is to pass a hash reference of named parameters:
239
240   @records = qsearch( {
241                         'table'       => 'table_name',
242                         'hashref'     => { 'field' => 'value'
243                                            'field' => { 'op'    => '<',
244                                                         'value' => '420',
245                                                       },
246                                          },
247
248                         #these are optional...
249                         'select'      => '*',
250                         'extra_sql'   => 'AND field = ? AND intfield = ?',
251                         'extra_param' => [ 'value', [ 5, 'int' ] ],
252                         'order_by'    => 'ORDER BY something',
253                         #'cache_obj'   => '', #optional
254                         'addl_from'   => 'LEFT JOIN othtable USING ( field )',
255                         'debug'       => 1,
256                       }
257                     );
258
259 Much code still uses old-style positional parameters, this is also probably
260 fine in the common case where there are only two parameters:
261
262   my @records = qsearch( 'table', { 'field' => 'value' } );
263
264 Also possible is an experimental LISTREF of PARAMS_HASHREFs for a UNION of
265 the individual PARAMS_HASHREF queries
266
267 ###oops, argh, FS::Record::new only lets us create database fields.
268 #Normal behaviour if SELECT is not specified is `*', as in
269 #C<SELECT * FROM table WHERE ...>.  However, there is an experimental new
270 #feature where you can specify SELECT - remember, the objects returned,
271 #although blessed into the appropriate `FS::TABLE' package, will only have the
272 #fields you specify.  This might have unwanted results if you then go calling
273 #regular FS::TABLE methods
274 #on it.
275
276 =cut
277
278 my %TYPE = (); #for debugging
279
280 sub _bind_type {
281   my($type, $value) = @_;
282
283   my $bind_type = { TYPE => SQL_VARCHAR };
284
285   if ( $type =~ /(big)?(int|serial)/i && $value =~ /^-?\d+(\.\d+)?$/ ) {
286
287     $bind_type = { TYPE => SQL_INTEGER };
288
289   } elsif ( $type =~ /^bytea$/i || $type =~ /(blob|varbinary)/i ) {
290
291     if ( driver_name eq 'Pg' ) {
292       no strict 'subs';
293       $bind_type = { pg_type => PG_BYTEA };
294     #} else {
295     #  $bind_type = ? #SQL_VARCHAR could be fine?
296     }
297
298   #DBD::Pg 1.49: Cannot bind ... unknown sql_type 6 with SQL_FLOAT
299   #fixed by DBD::Pg 2.11.8
300   #can change back to SQL_FLOAT in early-mid 2010, once everyone's upgraded
301   #(make a Tron test first)
302   } elsif ( _is_fs_float( $type, $value ) ) {
303
304     $bind_type = { TYPE => SQL_DECIMAL };
305
306   }
307
308   $bind_type;
309
310 }
311
312 sub _is_fs_float {
313   my($type, $value) = @_;
314   if ( ( $type =~ /(numeric)/i && $value =~ /^[+-]?\d+(\.\d+)?$/ ) ||
315        ( $type =~ /(real|float4)/i && $value =~ /[-+]?\d*\.?\d+([eE][-+]?\d+)?/)
316      ) {
317     return 1;
318   }
319   '';
320 }
321
322 sub qsearch {
323   my( @stable, @record, @cache );
324   my( @select, @extra_sql, @extra_param, @order_by, @addl_from );
325   my @debug = ();
326   my %union_options = ();
327   if ( ref($_[0]) eq 'ARRAY' ) {
328     my $optlist = shift;
329     %union_options = @_;
330     foreach my $href ( @$optlist ) {
331       push @stable,      ( $href->{'table'} or die "table name is required" );
332       push @record,      ( $href->{'hashref'}     || {} );
333       push @select,      ( $href->{'select'}      || '*' );
334       push @extra_sql,   ( $href->{'extra_sql'}   || '' );
335       push @extra_param, ( $href->{'extra_param'} || [] );
336       push @order_by,    ( $href->{'order_by'}    || '' );
337       push @cache,       ( $href->{'cache_obj'}   || '' );
338       push @addl_from,   ( $href->{'addl_from'}   || '' );
339       push @debug,       ( $href->{'debug'}       || '' );
340     }
341     die "at least one hashref is required" unless scalar(@stable);
342   } elsif ( ref($_[0]) eq 'HASH' ) {
343     my $opt = shift;
344     $stable[0]      = $opt->{'table'}       or die "table name is required";
345     $record[0]      = $opt->{'hashref'}     || {};
346     $select[0]      = $opt->{'select'}      || '*';
347     $extra_sql[0]   = $opt->{'extra_sql'}   || '';
348     $extra_param[0] = $opt->{'extra_param'} || [];
349     $order_by[0]    = $opt->{'order_by'}    || '';
350     $cache[0]       = $opt->{'cache_obj'}   || '';
351     $addl_from[0]   = $opt->{'addl_from'}   || '';
352     $debug[0]       = $opt->{'debug'}       || '';
353   } else {
354     ( $stable[0],
355       $record[0],
356       $select[0],
357       $extra_sql[0],
358       $cache[0],
359       $addl_from[0]
360     ) = @_;
361     $select[0] ||= '*';
362   }
363   my $cache = $cache[0];
364
365   my @statement = ();
366   my @value = ();
367   my @bind_type = ();
368   my $dbh = dbh;
369   foreach my $stable ( @stable ) {
370     #stop altering the caller's hashref
371     my $record      = { %{ shift(@record) || {} } };#and be liberal in receipt
372     my $select      = shift @select;
373     my $extra_sql   = shift @extra_sql;
374     my $extra_param = shift @extra_param;
375     my $order_by    = shift @order_by;
376     my $cache       = shift @cache;
377     my $addl_from   = shift @addl_from;
378     my $debug       = shift @debug;
379
380     #$stable =~ /^([\w\_]+)$/ or die "Illegal table: $table";
381     #for jsearch
382     $stable =~ /^([\w\s\(\)\.\,\=]+)$/ or die "Illegal table: $stable";
383     $stable = $1;
384
385     my $table = $cache ? $cache->table : $stable;
386     my $dbdef_table = dbdef->table($table)
387       or die "No schema for table $table found - ".
388              "do you need to run freeside-upgrade?";
389     my $pkey = $dbdef_table->primary_key;
390
391     my @real_fields = grep exists($record->{$_}), real_fields($table);
392
393     my $statement .= "SELECT $select FROM $stable";
394     $statement .= " $addl_from" if $addl_from;
395     if ( @real_fields ) {
396       $statement .= ' WHERE '. join(' AND ',
397         get_real_fields($table, $record, \@real_fields));
398     }
399
400     $statement .= " $extra_sql" if defined($extra_sql);
401     $statement .= " $order_by"  if defined($order_by);
402
403     push @statement, $statement;
404
405     warn "[debug]$me $statement\n" if $DEBUG > 1 || $debug;
406  
407
408     foreach my $field (
409       grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields
410     ) {
411
412       my $value = $record->{$field};
413       my $op = (ref($value) && $value->{op}) ? $value->{op} : '=';
414       $value = $value->{'value'} if ref($value);
415       my $type = dbdef->table($table)->column($field)->type;
416
417       my $bind_type = _bind_type($type, $value);
418
419       #if ( $DEBUG > 2 ) {
420       #  no strict 'refs';
421       #  %TYPE = map { &{"DBI::$_"}() => $_ } @{ $DBI::EXPORT_TAGS{sql_types} }
422       #    unless keys %TYPE;
423       #  warn "  bind_param $bind (for field $field), $value, TYPE $TYPE{$TYPE}\n";
424       #}
425
426       push @value, $value;
427       push @bind_type, $bind_type;
428
429     }
430
431     foreach my $param ( @$extra_param ) {
432       my $bind_type = { TYPE => SQL_VARCHAR };
433       my $value = $param;
434       if ( ref($param) ) {
435         $value = $param->[0];
436         my $type = $param->[1];
437         $bind_type = _bind_type($type, $value);
438       }
439       push @value, $value;
440       push @bind_type, $bind_type;
441     }
442   }
443
444   my $statement = join( ' ) UNION ( ', @statement );
445   $statement = "( $statement )" if scalar(@statement) > 1;
446   $statement .= " $union_options{order_by}" if $union_options{order_by};
447
448   my $sth = $dbh->prepare($statement)
449     or croak "$dbh->errstr doing $statement";
450
451   my $bind = 1;
452   foreach my $value ( @value ) {
453     my $bind_type = shift @bind_type;
454     $sth->bind_param($bind++, $value, $bind_type );
455   }
456
457 #  $sth->execute( map $record->{$_},
458 #    grep defined( $record->{$_} ) && $record->{$_} ne '', @fields
459 #  ) or croak "Error executing \"$statement\": ". $sth->errstr;
460
461   $sth->execute or croak "Error executing \"$statement\": ". $sth->errstr;
462
463   my $table = $stable[0];
464   my $pkey = '';
465   $table = '' if grep { $_ ne $table } @stable;
466   $pkey = dbdef->table($table)->primary_key if $table;
467
468   my %result;
469   tie %result, "Tie::IxHash";
470   my @stuff = @{ $sth->fetchall_arrayref( {} ) };
471   if ( $pkey && scalar(@stuff) && $stuff[0]->{$pkey} ) {
472     %result = map { $_->{$pkey}, $_ } @stuff;
473   } else {
474     @result{@stuff} = @stuff;
475   }
476
477   $sth->finish;
478
479   my @return;
480   if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
481     if ( eval 'FS::'. $table. '->can(\'new\')' eq \&new ) {
482       #derivied class didn't override new method, so this optimization is safe
483       if ( $cache ) {
484         @return = map {
485           new_or_cached( "FS::$table", { %{$_} }, $cache )
486         } values(%result);
487       } else {
488         @return = map {
489           new( "FS::$table", { %{$_} } )
490         } values(%result);
491       }
492     } else {
493       #okay, its been tested
494       # warn "untested code (class FS::$table uses custom new method)";
495       @return = map {
496         eval 'FS::'. $table. '->new( { %{$_} } )';
497       } values(%result);
498     }
499
500     # Check for encrypted fields and decrypt them.
501    ## only in the local copy, not the cached object
502     if ( $conf_encryption 
503          && eval 'defined(@FS::'. $table . '::encrypted_fields)' ) {
504       foreach my $record (@return) {
505         foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
506           next if $field eq 'payinfo' 
507                     && ($record->isa('FS::payinfo_transaction_Mixin') 
508                         || $record->isa('FS::payinfo_Mixin') )
509                     && $record->payby
510                     && !grep { $record->payby eq $_ } @encrypt_payby;
511           # Set it directly... This may cause a problem in the future...
512           $record->setfield($field, $record->decrypt($record->getfield($field)));
513         }
514       }
515     }
516   } else {
517     cluck "warning: FS::$table not loaded; returning FS::Record objects"
518       unless $nowarn_classload;
519     @return = map {
520       FS::Record->new( $table, { %{$_} } );
521     } values(%result);
522   }
523   return @return;
524 }
525
526 ## makes this easier to read
527
528 sub get_real_fields {
529   my $table = shift;
530   my $record = shift;
531   my $real_fields = shift;
532
533    ## this huge map was previously inline, just broke it out to help read the qsearch method, should be optimized for readability
534       return ( 
535       map {
536
537       my $op = '=';
538       my $column = $_;
539       my $type = dbdef->table($table)->column($column)->type;
540       my $value = $record->{$column};
541       $value = $value->{'value'} if ref($value);
542       if ( ref($record->{$_}) ) {
543         $op = $record->{$_}{'op'} if $record->{$_}{'op'};
544         #$op = 'LIKE' if $op =~ /^ILIKE$/i && driver_name ne 'Pg';
545         if ( uc($op) eq 'ILIKE' ) {
546           $op = 'LIKE';
547           $record->{$_}{'value'} = lc($record->{$_}{'value'});
548           $column = "LOWER($_)";
549         }
550         $record->{$_} = $record->{$_}{'value'}
551       }
552
553       if ( ! defined( $record->{$_} ) || $record->{$_} eq '' ) {
554         if ( $op eq '=' ) {
555           if ( driver_name eq 'Pg' ) {
556             if ( $type =~ /(int|numeric|real|float4|(big)?serial)/i ) {
557               qq-( $column IS NULL )-;
558             } else {
559               qq-( $column IS NULL OR $column = '' )-;
560             }
561           } else {
562             qq-( $column IS NULL OR $column = "" )-;
563           }
564         } elsif ( $op eq '!=' ) {
565           if ( driver_name eq 'Pg' ) {
566             if ( $type =~ /(int|numeric|real|float4|(big)?serial)/i ) {
567               qq-( $column IS NOT NULL )-;
568             } else {
569               qq-( $column IS NOT NULL AND $column != '' )-;
570             }
571           } else {
572             qq-( $column IS NOT NULL AND $column != "" )-;
573           }
574         } else {
575           if ( driver_name eq 'Pg' ) {
576             qq-( $column $op '' )-;
577           } else {
578             qq-( $column $op "" )-;
579           }
580         }
581       } elsif ( $op eq '!=' ) {
582         qq-( $column IS NULL OR $column != ? )-;
583       #if this needs to be re-enabled, it needs to use a custom op like
584       #"APPROX=" or something (better name?, not '=', to avoid affecting other
585       # searches
586       #} elsif ( $op eq 'APPROX=' && _is_fs_float( $type, $value ) ) {
587       #  ( "$column <= ?", "$column >= ?" );
588       } else {
589         "$column $op ?";
590       }
591     } @{ $real_fields } );  
592 }
593
594 =item by_key PRIMARY_KEY_VALUE
595
596 This is a class method that returns the record with the given primary key
597 value.  This method is only useful in FS::Record subclasses.  For example:
598
599   my $cust_main = FS::cust_main->by_key(1); # retrieve customer with custnum 1
600
601 is equivalent to:
602
603   my $cust_main = qsearchs('cust_main', { 'custnum' => 1 } );
604
605 =cut
606
607 sub by_key {
608   my ($class, $pkey_value) = @_;
609
610   my $table = $class->table
611     or croak "No table for $class found";
612
613   my $dbdef_table = dbdef->table($table)
614     or die "No schema for table $table found - ".
615            "do you need to create it or run dbdef-create?";
616   my $pkey = $dbdef_table->primary_key
617     or die "No primary key for table $table";
618
619   return qsearchs($table, { $pkey => $pkey_value });
620 }
621
622 =item jsearch TABLE, HASHREF, SELECT, EXTRA_SQL, PRIMARY_TABLE, PRIMARY_KEY
623
624 Experimental JOINed search method.  Using this method, you can execute a
625 single SELECT spanning multiple tables, and cache the results for subsequent
626 method calls.  Interface will almost definately change in an incompatible
627 fashion.
628
629 Arguments: 
630
631 =cut
632
633 sub jsearch {
634   my($table, $record, $select, $extra_sql, $ptable, $pkey ) = @_;
635   my $cache = FS::SearchCache->new( $ptable, $pkey );
636   my %saw;
637   ( $cache,
638     grep { !$saw{$_->getfield($pkey)}++ }
639       qsearch($table, $record, $select, $extra_sql, $cache )
640   );
641 }
642
643 =item qsearchs PARAMS_HASHREF | TABLE, HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ, ADDL_FROM
644
645 Same as qsearch, except that if more than one record matches, it B<carp>s but
646 returns the first.  If this happens, you either made a logic error in asking
647 for a single item, or your data is corrupted.
648
649 =cut
650
651 sub qsearchs { # $result_record = &FS::Record:qsearchs('table',\%hash);
652   my $table = $_[0];
653   my(@result) = qsearch(@_);
654   cluck "warning: Multiple records in scalar search ($table)"
655     if scalar(@result) > 1;
656   #should warn more vehemently if the search was on a primary key?
657   scalar(@result) ? ($result[0]) : ();
658 }
659
660 =back
661
662 =head1 METHODS
663
664 =over 4
665
666 =item table
667
668 Returns the table name.
669
670 =cut
671
672 sub table {
673 #  cluck "warning: FS::Record::table deprecated; supply one in subclass!";
674   my $self = shift;
675   $self -> {'Table'};
676 }
677
678 =item dbdef_table
679
680 Returns the DBIx::DBSchema::Table object for the table.
681
682 =cut
683
684 sub dbdef_table {
685   my($self)=@_;
686   my($table)=$self->table;
687   dbdef->table($table);
688 }
689
690 =item primary_key
691
692 Returns the primary key for the table.
693
694 =cut
695
696 sub primary_key {
697   my $self = shift;
698   my $pkey = $self->dbdef_table->primary_key;
699 }
700
701 =item get, getfield COLUMN
702
703 Returns the value of the column/field/key COLUMN.
704
705 =cut
706
707 sub get {
708   my($self,$field) = @_;
709   # to avoid "Use of unitialized value" errors
710   if ( defined ( $self->{Hash}->{$field} ) ) {
711     $self->{Hash}->{$field};
712   } else { 
713     '';
714   }
715 }
716 sub getfield {
717   my $self = shift;
718   $self->get(@_);
719 }
720
721 =item set, setfield COLUMN, VALUE
722
723 Sets the value of the column/field/key COLUMN to VALUE.  Returns VALUE.
724
725 =cut
726
727 sub set { 
728   my($self,$field,$value) = @_;
729   $self->{'modified'} = 1;
730   $self->{'Hash'}->{$field} = $value;
731 }
732 sub setfield {
733   my $self = shift;
734   $self->set(@_);
735 }
736
737 =item exists COLUMN
738
739 Returns true if the column/field/key COLUMN exists.
740
741 =cut
742
743 sub exists {
744   my($self,$field) = @_;
745   exists($self->{Hash}->{$field});
746 }
747
748 =item AUTLOADED METHODS
749
750 $record->column is a synonym for $record->get('column');
751
752 $record->column('value') is a synonym for $record->set('column','value');
753
754 =cut
755
756 # readable/safe
757 sub AUTOLOAD {
758   my($self,$value)=@_;
759   my($field)=$AUTOLOAD;
760   $field =~ s/.*://;
761   if ( defined($value) ) {
762     confess "errant AUTOLOAD $field for $self (arg $value)"
763       unless blessed($self) && $self->can('setfield');
764     $self->setfield($field,$value);
765   } else {
766     confess "errant AUTOLOAD $field for $self (no args)"
767       unless blessed($self) && $self->can('getfield');
768     $self->getfield($field);
769   }    
770 }
771
772 # efficient
773 #sub AUTOLOAD {
774 #  my $field = $AUTOLOAD;
775 #  $field =~ s/.*://;
776 #  if ( defined($_[1]) ) {
777 #    $_[0]->setfield($field, $_[1]);
778 #  } else {
779 #    $_[0]->getfield($field);
780 #  }    
781 #}
782
783 =item hash
784
785 Returns a list of the column/value pairs, usually for assigning to a new hash.
786
787 To make a distinct duplicate of an FS::Record object, you can do:
788
789     $new = new FS::Record ( $old->table, { $old->hash } );
790
791 =cut
792
793 sub hash {
794   my($self) = @_;
795   confess $self. ' -> hash: Hash attribute is undefined'
796     unless defined($self->{'Hash'});
797   %{ $self->{'Hash'} }; 
798 }
799
800 =item hashref
801
802 Returns a reference to the column/value hash.  This may be deprecated in the
803 future; if there's a reason you can't just use the autoloaded or get/set
804 methods, speak up.
805
806 =cut
807
808 sub hashref {
809   my($self) = @_;
810   $self->{'Hash'};
811 }
812
813 =item modified
814
815 Returns true if any of this object's values have been modified with set (or via
816 an autoloaded method).  Doesn't yet recognize when you retreive a hashref and
817 modify that.
818
819 =cut
820
821 sub modified {
822   my $self = shift;
823   $self->{'modified'};
824 }
825
826 =item select_for_update
827
828 Selects this record with the SQL "FOR UPDATE" command.  This can be useful as
829 a mutex.
830
831 =cut
832
833 sub select_for_update {
834   my $self = shift;
835   my $primary_key = $self->primary_key;
836   qsearchs( {
837     'select'    => '*',
838     'table'     => $self->table,
839     'hashref'   => { $primary_key => $self->$primary_key() },
840     'extra_sql' => 'FOR UPDATE',
841   } );
842 }
843
844 =item lock_table
845
846 Locks this table with a database-driver specific lock method.  This is used
847 as a mutex in order to do a duplicate search.
848
849 For PostgreSQL, does "LOCK TABLE tablename IN SHARE ROW EXCLUSIVE MODE".
850
851 For MySQL, does a SELECT FOR UPDATE on the duplicate_lock table.
852
853 Errors are fatal; no useful return value.
854
855 Note: To use this method for new tables other than svc_acct and svc_phone,
856 edit freeside-upgrade and add those tables to the duplicate_lock list.
857
858 =cut
859
860 sub lock_table {
861   my $self = shift;
862   my $table = $self->table;
863
864   warn "$me locking $table table\n" if $DEBUG;
865
866   if ( driver_name =~ /^Pg/i ) {
867
868     dbh->do("LOCK TABLE $table IN SHARE ROW EXCLUSIVE MODE")
869       or die dbh->errstr;
870
871   } elsif ( driver_name =~ /^mysql/i ) {
872
873     dbh->do("SELECT * FROM duplicate_lock
874                WHERE lockname = '$table'
875                FOR UPDATE"
876            ) or die dbh->errstr;
877
878   } else {
879
880     die "unknown database ". driver_name. "; don't know how to lock table";
881
882   }
883
884   warn "$me acquired $table table lock\n" if $DEBUG;
885
886 }
887
888 =item insert
889
890 Inserts this record to the database.  If there is an error, returns the error,
891 otherwise returns false.
892
893 =cut
894
895 sub insert {
896   my $self = shift;
897   my $saved = {};
898
899   warn "$self -> insert" if $DEBUG;
900
901   my $error = $self->check;
902   return $error if $error;
903
904   #single-field non-null unique keys are given a value if empty
905   #(like MySQL's AUTO_INCREMENT or Pg SERIAL)
906   foreach ( $self->dbdef_table->unique_singles) {
907     next if $self->getfield($_);
908     next if $self->dbdef_table->column($_)->null eq 'NULL';
909     $self->unique($_);
910   }
911
912   #and also the primary key, if the database isn't going to
913   my $primary_key = $self->dbdef_table->primary_key;
914   my $db_seq = 0;
915   if ( $primary_key ) {
916     my $col = $self->dbdef_table->column($primary_key);
917
918     $db_seq =
919       uc($col->type) =~ /^(BIG)?SERIAL\d?/
920       || ( driver_name eq 'Pg'
921              && defined($col->default)
922              && $col->quoted_default =~ /^nextval\(/i
923          )
924       || ( driver_name eq 'mysql'
925              && defined($col->local)
926              && $col->local =~ /AUTO_INCREMENT/i
927          );
928     $self->unique($primary_key) unless $self->getfield($primary_key) || $db_seq;
929   }
930
931   my $table = $self->table;
932   
933   # Encrypt before the database
934   if (    defined(eval '@FS::'. $table . '::encrypted_fields')
935        && scalar( eval '@FS::'. $table . '::encrypted_fields')
936        && $conf->exists('encryption')
937   ) {
938     foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
939       next if $field eq 'payinfo' 
940                 && ($self->isa('FS::payinfo_transaction_Mixin') 
941                     || $self->isa('FS::payinfo_Mixin') )
942                 && $self->payby
943                 && !grep { $self->payby eq $_ } @encrypt_payby;
944       $saved->{$field} = $self->getfield($field);
945       $self->setfield($field, $self->encrypt($self->getfield($field)));
946     }
947   }
948
949   #false laziness w/delete
950   my @real_fields =
951     grep { defined($self->getfield($_)) && $self->getfield($_) ne "" }
952     real_fields($table)
953   ;
954   my @values = map { _quote( $self->getfield($_), $table, $_) } @real_fields;
955   #eslaf
956
957   my $statement = "INSERT INTO $table ";
958   if ( @real_fields ) {
959     $statement .=
960       "( ".
961         join( ', ', @real_fields ).
962       ") VALUES (".
963         join( ', ', @values ).
964        ")"
965     ;
966   } else {
967     $statement .= 'DEFAULT VALUES';
968   }
969   warn "[debug]$me $statement\n" if $DEBUG > 1;
970   my $sth = dbh->prepare($statement) or return dbh->errstr;
971
972   local $SIG{HUP} = 'IGNORE';
973   local $SIG{INT} = 'IGNORE';
974   local $SIG{QUIT} = 'IGNORE'; 
975   local $SIG{TERM} = 'IGNORE';
976   local $SIG{TSTP} = 'IGNORE';
977   local $SIG{PIPE} = 'IGNORE';
978
979   $sth->execute or return $sth->errstr;
980
981   # get inserted id from the database, if applicable & needed
982   if ( $db_seq && ! $self->getfield($primary_key) ) {
983     warn "[debug]$me retreiving sequence from database\n" if $DEBUG;
984   
985     my $insertid = '';
986
987     if ( driver_name eq 'Pg' ) {
988
989       #my $oid = $sth->{'pg_oid_status'};
990       #my $i_sql = "SELECT $primary_key FROM $table WHERE oid = ?";
991
992       my $default = $self->dbdef_table->column($primary_key)->quoted_default;
993       unless ( $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i ) {
994         dbh->rollback if $FS::UID::AutoCommit;
995         return "can't parse $table.$primary_key default value".
996                " for sequence name: $default";
997       }
998       my $sequence = $1;
999
1000       my $i_sql = "SELECT currval('$sequence')";
1001       my $i_sth = dbh->prepare($i_sql) or do {
1002         dbh->rollback if $FS::UID::AutoCommit;
1003         return dbh->errstr;
1004       };
1005       $i_sth->execute() or do { #$i_sth->execute($oid)
1006         dbh->rollback if $FS::UID::AutoCommit;
1007         return $i_sth->errstr;
1008       };
1009       $insertid = $i_sth->fetchrow_arrayref->[0];
1010
1011     } elsif ( driver_name eq 'mysql' ) {
1012
1013       $insertid = dbh->{'mysql_insertid'};
1014       # work around mysql_insertid being null some of the time, ala RT :/
1015       unless ( $insertid ) {
1016         warn "WARNING: DBD::mysql didn't return mysql_insertid; ".
1017              "using SELECT LAST_INSERT_ID();";
1018         my $i_sql = "SELECT LAST_INSERT_ID()";
1019         my $i_sth = dbh->prepare($i_sql) or do {
1020           dbh->rollback if $FS::UID::AutoCommit;
1021           return dbh->errstr;
1022         };
1023         $i_sth->execute or do {
1024           dbh->rollback if $FS::UID::AutoCommit;
1025           return $i_sth->errstr;
1026         };
1027         $insertid = $i_sth->fetchrow_arrayref->[0];
1028       }
1029
1030     } else {
1031
1032       dbh->rollback if $FS::UID::AutoCommit;
1033       return "don't know how to retreive inserted ids from ". driver_name. 
1034              ", try using counterfiles (maybe run dbdef-create?)";
1035
1036     }
1037
1038     $self->setfield($primary_key, $insertid);
1039
1040   }
1041
1042   my $h_sth;
1043   if ( defined dbdef->table('h_'. $table) ) {
1044     my $h_statement = $self->_h_statement('insert');
1045     warn "[debug]$me $h_statement\n" if $DEBUG > 2;
1046     $h_sth = dbh->prepare($h_statement) or do {
1047       dbh->rollback if $FS::UID::AutoCommit;
1048       return dbh->errstr;
1049     };
1050   } else {
1051     $h_sth = '';
1052   }
1053   $h_sth->execute or return $h_sth->errstr if $h_sth;
1054
1055   dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
1056
1057   # Now that it has been saved, reset the encrypted fields so that $new 
1058   # can still be used.
1059   foreach my $field (keys %{$saved}) {
1060     $self->setfield($field, $saved->{$field});
1061   }
1062
1063   '';
1064 }
1065
1066 =item add
1067
1068 Depriciated (use insert instead).
1069
1070 =cut
1071
1072 sub add {
1073   cluck "warning: FS::Record::add deprecated!";
1074   insert @_; #call method in this scope
1075 }
1076
1077 =item delete
1078
1079 Delete this record from the database.  If there is an error, returns the error,
1080 otherwise returns false.
1081
1082 =cut
1083
1084 sub delete {
1085   my $self = shift;
1086
1087   my $statement = "DELETE FROM ". $self->table. " WHERE ". join(' AND ',
1088     map {
1089       $self->getfield($_) eq ''
1090         #? "( $_ IS NULL OR $_ = \"\" )"
1091         ? ( driver_name eq 'Pg'
1092               ? "$_ IS NULL"
1093               : "( $_ IS NULL OR $_ = \"\" )"
1094           )
1095         : "$_ = ". _quote($self->getfield($_),$self->table,$_)
1096     } ( $self->dbdef_table->primary_key )
1097           ? ( $self->dbdef_table->primary_key)
1098           : real_fields($self->table)
1099   );
1100   warn "[debug]$me $statement\n" if $DEBUG > 1;
1101   my $sth = dbh->prepare($statement) or return dbh->errstr;
1102
1103   my $h_sth;
1104   if ( defined dbdef->table('h_'. $self->table) ) {
1105     my $h_statement = $self->_h_statement('delete');
1106     warn "[debug]$me $h_statement\n" if $DEBUG > 2;
1107     $h_sth = dbh->prepare($h_statement) or return dbh->errstr;
1108   } else {
1109     $h_sth = '';
1110   }
1111
1112   my $primary_key = $self->dbdef_table->primary_key;
1113
1114   local $SIG{HUP} = 'IGNORE';
1115   local $SIG{INT} = 'IGNORE';
1116   local $SIG{QUIT} = 'IGNORE'; 
1117   local $SIG{TERM} = 'IGNORE';
1118   local $SIG{TSTP} = 'IGNORE';
1119   local $SIG{PIPE} = 'IGNORE';
1120
1121   my $rc = $sth->execute or return $sth->errstr;
1122   #not portable #return "Record not found, statement:\n$statement" if $rc eq "0E0";
1123   $h_sth->execute or return $h_sth->errstr if $h_sth;
1124   
1125   dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
1126
1127   #no need to needlessly destoy the data either (causes problems actually)
1128   #undef $self; #no need to keep object!
1129
1130   '';
1131 }
1132
1133 =item del
1134
1135 Depriciated (use delete instead).
1136
1137 =cut
1138
1139 sub del {
1140   cluck "warning: FS::Record::del deprecated!";
1141   &delete(@_); #call method in this scope
1142 }
1143
1144 =item replace OLD_RECORD
1145
1146 Replace the OLD_RECORD with this one in the database.  If there is an error,
1147 returns the error, otherwise returns false.
1148
1149 =cut
1150
1151 sub replace {
1152   my ($new, $old) = (shift, shift);
1153
1154   $old = $new->replace_old unless defined($old);
1155
1156   warn "[debug]$me $new ->replace $old\n" if $DEBUG;
1157
1158   if ( $new->can('replace_check') ) {
1159     my $error = $new->replace_check($old);
1160     return $error if $error;
1161   }
1162
1163   return "Records not in same table!" unless $new->table eq $old->table;
1164
1165   my $primary_key = $old->dbdef_table->primary_key;
1166   return "Can't change primary key $primary_key ".
1167          'from '. $old->getfield($primary_key).
1168          ' to ' . $new->getfield($primary_key)
1169     if $primary_key
1170        && ( $old->getfield($primary_key) ne $new->getfield($primary_key) );
1171
1172   my $error = $new->check;
1173   return $error if $error;
1174   
1175   # Encrypt for replace
1176   my $saved = {};
1177   if (    $conf->exists('encryption')
1178        && defined(eval '@FS::'. $new->table . '::encrypted_fields')
1179        && scalar( eval '@FS::'. $new->table . '::encrypted_fields')
1180   ) {
1181     foreach my $field (eval '@FS::'. $new->table . '::encrypted_fields') {
1182       next if $field eq 'payinfo' 
1183                 && ($new->isa('FS::payinfo_transaction_Mixin') 
1184                     || $new->isa('FS::payinfo_Mixin') )
1185                 && $new->payby
1186                 && !grep { $new->payby eq $_ } @encrypt_payby;
1187       $saved->{$field} = $new->getfield($field);
1188       $new->setfield($field, $new->encrypt($new->getfield($field)));
1189     }
1190   }
1191
1192   #my @diff = grep $new->getfield($_) ne $old->getfield($_), $old->fields;
1193   my %diff = map { ($new->getfield($_) ne $old->getfield($_))
1194                    ? ($_, $new->getfield($_)) : () } $old->fields;
1195                    
1196   unless (keys(%diff) || $no_update_diff ) {
1197     carp "[warning]$me ". ref($new)."->replace ".
1198            ( $primary_key ? "$primary_key ".$new->get($primary_key) : '' ).
1199          ": records identical"
1200       unless $nowarn_identical;
1201     return '';
1202   }
1203
1204   my $statement = "UPDATE ". $old->table. " SET ". join(', ',
1205     map {
1206       "$_ = ". _quote($new->getfield($_),$old->table,$_) 
1207     } real_fields($old->table)
1208   ). ' WHERE '.
1209     join(' AND ',
1210       map {
1211
1212         if ( $old->getfield($_) eq '' ) {
1213
1214          #false laziness w/qsearch
1215          if ( driver_name eq 'Pg' ) {
1216             my $type = $old->dbdef_table->column($_)->type;
1217             if ( $type =~ /(int|(big)?serial)/i ) {
1218               qq-( $_ IS NULL )-;
1219             } else {
1220               qq-( $_ IS NULL OR $_ = '' )-;
1221             }
1222           } else {
1223             qq-( $_ IS NULL OR $_ = "" )-;
1224           }
1225
1226         } else {
1227           "$_ = ". _quote($old->getfield($_),$old->table,$_);
1228         }
1229
1230       } ( $primary_key ? ( $primary_key ) : real_fields($old->table) )
1231     )
1232   ;
1233   warn "[debug]$me $statement\n" if $DEBUG > 1;
1234   my $sth = dbh->prepare($statement) or return dbh->errstr;
1235
1236   my $h_old_sth;
1237   if ( defined dbdef->table('h_'. $old->table) ) {
1238     my $h_old_statement = $old->_h_statement('replace_old');
1239     warn "[debug]$me $h_old_statement\n" if $DEBUG > 2;
1240     $h_old_sth = dbh->prepare($h_old_statement) or return dbh->errstr;
1241   } else {
1242     $h_old_sth = '';
1243   }
1244
1245   my $h_new_sth;
1246   if ( defined dbdef->table('h_'. $new->table) ) {
1247     my $h_new_statement = $new->_h_statement('replace_new');
1248     warn "[debug]$me $h_new_statement\n" if $DEBUG > 2;
1249     $h_new_sth = dbh->prepare($h_new_statement) or return dbh->errstr;
1250   } else {
1251     $h_new_sth = '';
1252   }
1253
1254   local $SIG{HUP} = 'IGNORE';
1255   local $SIG{INT} = 'IGNORE';
1256   local $SIG{QUIT} = 'IGNORE'; 
1257   local $SIG{TERM} = 'IGNORE';
1258   local $SIG{TSTP} = 'IGNORE';
1259   local $SIG{PIPE} = 'IGNORE';
1260
1261   my $rc = $sth->execute or return $sth->errstr;
1262   #not portable #return "Record not found (or records identical)." if $rc eq "0E0";
1263   $h_old_sth->execute or return $h_old_sth->errstr if $h_old_sth;
1264   $h_new_sth->execute or return $h_new_sth->errstr if $h_new_sth;
1265
1266   dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
1267
1268   # Now that it has been saved, reset the encrypted fields so that $new 
1269   # can still be used.
1270   foreach my $field (keys %{$saved}) {
1271     $new->setfield($field, $saved->{$field});
1272   }
1273
1274   '';
1275
1276 }
1277
1278 sub replace_old {
1279   my( $self ) = shift;
1280   warn "[$me] replace called with no arguments; autoloading old record\n"
1281     if $DEBUG;
1282
1283   my $primary_key = $self->dbdef_table->primary_key;
1284   if ( $primary_key ) {
1285     $self->by_key( $self->$primary_key() ) #this is what's returned
1286       or croak "can't find ". $self->table. ".$primary_key ".
1287         $self->$primary_key();
1288   } else {
1289     croak $self->table. " has no primary key; pass old record as argument";
1290   }
1291
1292 }
1293
1294 =item rep
1295
1296 Depriciated (use replace instead).
1297
1298 =cut
1299
1300 sub rep {
1301   cluck "warning: FS::Record::rep deprecated!";
1302   replace @_; #call method in this scope
1303 }
1304
1305 =item check
1306
1307 Checks custom fields. Subclasses should still provide a check method to validate
1308 non-custom fields, foreign keys, etc., and call this method via $self->SUPER::check.
1309
1310 =cut
1311
1312 sub check { 
1313     my $self = shift;
1314     foreach my $field ($self->virtual_fields) {
1315         my $error = $self->ut_textn($field);
1316         return $error if $error;
1317     }
1318     '';
1319 }
1320
1321 =item virtual_fields [ TABLE ]
1322
1323 Returns a list of virtual fields defined for the table.  This should not 
1324 be exported, and should only be called as an instance or class method.
1325
1326 =cut
1327
1328 sub virtual_fields {
1329   my $self = shift;
1330   my $table;
1331   $table = $self->table or confess "virtual_fields called on non-table";
1332
1333   confess "Unknown table $table" unless dbdef->table($table);
1334
1335   return () unless dbdef->table('part_virtual_field');
1336
1337   unless ( $virtual_fields_cache{$table} ) {
1338     my $concat = [ "'cf_'", "name" ];
1339     my $query = "SELECT ".concat_sql($concat).' from part_virtual_field ' .
1340                 "WHERE dbtable = '$table'";
1341     my $dbh = dbh;
1342     my $result = $dbh->selectcol_arrayref($query);
1343     confess "Error executing virtual fields query: $query: ". $dbh->errstr
1344       if $dbh->err;
1345     $virtual_fields_cache{$table} = $result;
1346   }
1347
1348   @{$virtual_fields_cache{$table}};
1349
1350 }
1351
1352 =item process_batch_import JOB OPTIONS_HASHREF PARAMS
1353
1354 Processes a batch import as a queued JSRPC job
1355
1356 JOB is an FS::queue entry.
1357
1358 OPTIONS_HASHREF can have the following keys:
1359
1360 =over 4
1361
1362 =item table
1363
1364 Table name (required).
1365
1366 =item params
1367
1368 Listref of field names for static fields.  They will be given values from the
1369 PARAMS hashref and passed as a "params" hashref to batch_import.
1370
1371 =item formats
1372
1373 Formats hashref.  Keys are field names, values are listrefs that define the
1374 format.
1375
1376 Each listref value can be a column name or a code reference.  Coderefs are run
1377 with the row object, data and a FS::Conf object as the three parameters.
1378 For example, this coderef does the same thing as using the "columnname" string:
1379
1380   sub {
1381     my( $record, $data, $conf ) = @_;
1382     $record->columnname( $data );
1383   },
1384
1385 Coderefs are run after all "column name" fields are assigned.
1386
1387 =item format_types
1388
1389 Optional format hashref of types.  Keys are field names, values are "csv",
1390 "xls" or "fixedlength".  Overrides automatic determination of file type
1391 from extension.
1392
1393 =item format_headers
1394
1395 Optional format hashref of header lines.  Keys are field names, values are 0
1396 for no header, 1 to ignore the first line, or to higher numbers to ignore that
1397 number of lines.
1398
1399 =item format_sep_chars
1400
1401 Optional format hashref of CSV sep_chars.  Keys are field names, values are the
1402 CSV separation character.
1403
1404 =item format_fixedlenth_formats
1405
1406 Optional format hashref of fixed length format defintiions.  Keys are field
1407 names, values Parse::FixedLength listrefs of field definitions.
1408
1409 =item default_csv
1410
1411 Set true to default to CSV file type if the filename does not contain a
1412 recognizable ".csv" or ".xls" extension (and type is not pre-specified by
1413 format_types).
1414
1415 =back
1416
1417 PARAMS is a base64-encoded Storable string containing the POSTed data as
1418 a hash ref.  It normally contains at least one field, "uploaded files",
1419 generated by /elements/file-upload.html and containing the list of uploaded
1420 files.  Currently only supports a single file named "file".
1421
1422 =cut
1423
1424 use Storable qw(thaw);
1425 use Data::Dumper;
1426 use MIME::Base64;
1427 sub process_batch_import {
1428   my($job, $opt) = ( shift, shift );
1429
1430   my $table = $opt->{table};
1431   my @pass_params = $opt->{params} ? @{ $opt->{params} } : ();
1432   my %formats = %{ $opt->{formats} };
1433
1434   my $param = thaw(decode_base64(shift));
1435   warn Dumper($param) if $DEBUG;
1436   
1437   my $files = $param->{'uploaded_files'}
1438     or die "No files provided.\n";
1439
1440   my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
1441
1442   my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
1443   my $file = $dir. $files{'file'};
1444
1445   my %iopt = (
1446     #class-static
1447     table                      => $table,
1448     formats                    => \%formats,
1449     format_types               => $opt->{format_types},
1450     format_headers             => $opt->{format_headers},
1451     format_sep_chars           => $opt->{format_sep_chars},
1452     format_fixedlength_formats => $opt->{format_fixedlength_formats},
1453     format_xml_formats         => $opt->{format_xml_formats},
1454     format_asn_formats         => $opt->{format_asn_formats},
1455     format_row_callbacks       => $opt->{format_row_callbacks},
1456     #per-import
1457     job                        => $job,
1458     file                       => $file,
1459     #type                       => $type,
1460     format                     => $param->{format},
1461     params                     => { map { $_ => $param->{$_} } @pass_params },
1462     #?
1463     default_csv                => $opt->{default_csv},
1464     postinsert_callback        => $opt->{postinsert_callback},
1465   );
1466
1467   if ( $opt->{'batch_namecol'} ) {
1468     $iopt{'batch_namevalue'} = $param->{ $opt->{'batch_namecol'} };
1469     $iopt{$_} = $opt->{$_} foreach qw( batch_keycol batch_table batch_namecol );
1470   }
1471
1472   my $error = FS::Record::batch_import( \%iopt );
1473
1474   unlink $file;
1475
1476   die "$error\n" if $error;
1477 }
1478
1479 =item batch_import PARAM_HASHREF
1480
1481 Class method for batch imports.  Available params:
1482
1483 =over 4
1484
1485 =item table
1486
1487 =item format - usual way to specify import, with this format string selecting data from the formats and format_* info hashes
1488
1489 =item formats
1490
1491 =item format_types
1492
1493 =item format_headers
1494
1495 =item format_sep_chars
1496
1497 =item format_fixedlength_formats
1498
1499 =item format_row_callbacks
1500
1501 =item fields - Alternate way to specify import, specifying import fields directly as a listref
1502
1503 =item preinsert_callback
1504
1505 =item postinsert_callback
1506
1507 =item params
1508
1509 =item job
1510
1511 FS::queue object, will be updated with progress
1512
1513 =item file
1514
1515 =item type
1516
1517 csv, xls, fixedlength, xml
1518
1519 =item empty_ok
1520
1521 =back
1522
1523 =cut
1524
1525 sub batch_import {
1526   my $param = shift;
1527
1528   warn "$me batch_import call with params: \n". Dumper($param)
1529     if $DEBUG;
1530
1531   my $table   = $param->{table};
1532
1533   my $job     = $param->{job};
1534   my $file    = $param->{file};
1535   my $params  = $param->{params} || {};
1536
1537   my( $type, $header, $sep_char,
1538       $fixedlength_format, $xml_format, $asn_format,
1539       $row_callback, @fields );
1540
1541   my $postinsert_callback = '';
1542   $postinsert_callback = $param->{'postinsert_callback'}
1543           if $param->{'postinsert_callback'};
1544   my $preinsert_callback = '';
1545   $preinsert_callback = $param->{'preinsert_callback'}
1546           if $param->{'preinsert_callback'};
1547
1548   if ( $param->{'format'} ) {
1549
1550     my $format  = $param->{'format'};
1551     my $formats = $param->{formats};
1552     die "unknown format $format" unless exists $formats->{ $format };
1553
1554     $type = $param->{'format_types'}
1555             ? $param->{'format_types'}{ $format }
1556             : $param->{type} || 'csv';
1557
1558
1559     $header = $param->{'format_headers'}
1560                ? $param->{'format_headers'}{ $param->{'format'} }
1561                : 0;
1562
1563     $sep_char = $param->{'format_sep_chars'}
1564                   ? $param->{'format_sep_chars'}{ $param->{'format'} }
1565                   : ',';
1566
1567     $fixedlength_format =
1568       $param->{'format_fixedlength_formats'}
1569         ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
1570         : '';
1571
1572     $xml_format =
1573       $param->{'format_xml_formats'}
1574         ? $param->{'format_xml_formats'}{ $param->{'format'} }
1575         : '';
1576
1577     $asn_format =
1578       $param->{'format_asn_formats'}
1579         ? $param->{'format_asn_formats'}{ $param->{'format'} }
1580         : '';
1581
1582     $row_callback =
1583       $param->{'format_row_callbacks'}
1584         ? $param->{'format_row_callbacks'}{ $param->{'format'} }
1585         : '';
1586
1587     @fields = @{ $formats->{ $format } };
1588
1589   } elsif ( $param->{'fields'} ) {
1590
1591     $type = ''; #infer from filename
1592     $header = 0;
1593     $sep_char = ',';
1594     $fixedlength_format = '';
1595     $row_callback = '';
1596     @fields = @{ $param->{'fields'} };
1597
1598   } else {
1599     die "neither format nor fields specified";
1600   }
1601
1602   #my $file    = $param->{file};
1603
1604   unless ( $type ) {
1605     if ( $file =~ /\.(\w+)$/i ) {
1606       $type = lc($1);
1607     } else {
1608       #or error out???
1609       warn "can't parse file type from filename $file; defaulting to CSV";
1610       $type = 'csv';
1611     }
1612     $type = 'csv'
1613       if $param->{'default_csv'} && $type ne 'xls';
1614   }
1615
1616
1617   my $row = 0;
1618   my $count;
1619   my $parser;
1620   my @buffer = ();
1621   my $asn_header_buffer;
1622   if ( $type eq 'csv' || $type eq 'fixedlength' ) {
1623
1624     if ( $type eq 'csv' ) {
1625
1626       my %attr = ();
1627       $attr{sep_char} = $sep_char if $sep_char;
1628       $parser = new Text::CSV_XS \%attr;
1629
1630     } elsif ( $type eq 'fixedlength' ) {
1631
1632       eval "use Parse::FixedLength;";
1633       die $@ if $@;
1634       $parser = Parse::FixedLength->new($fixedlength_format);
1635
1636     }
1637     else {
1638       die "Unknown file type $type\n";
1639     }
1640
1641     @buffer = split(/\r?\n/, slurp($file) );
1642     splice(@buffer, 0, ($header || 0) );
1643     $count = scalar(@buffer);
1644
1645   } elsif ( $type eq 'xls' ) {
1646
1647     eval "use Spreadsheet::ParseExcel;";
1648     die $@ if $@;
1649
1650     eval "use DateTime::Format::Excel;";
1651     #for now, just let the error be thrown if it is used, since only CDR
1652     # formats bill_west and troop use it, not other excel-parsing things
1653     #die $@ if $@;
1654
1655     my $excel = Spreadsheet::ParseExcel::Workbook->new->Parse($file);
1656
1657     $parser = $excel->{Worksheet}[0]; #first sheet
1658
1659     $count = $parser->{MaxRow} || $parser->{MinRow};
1660     $count++;
1661
1662     $row = $header || 0;
1663
1664   } elsif ( $type eq 'xml' ) {
1665
1666     # FS::pay_batch
1667     eval "use XML::Simple;";
1668     die $@ if $@;
1669     my $xmlrow = $xml_format->{'xmlrow'};
1670     $parser = $xml_format->{'xmlkeys'};
1671     die 'no xmlkeys specified' unless ref $parser eq 'ARRAY';
1672     my $data = XML::Simple::XMLin(
1673       $file,
1674       'SuppressEmpty' => '', #sets empty values to ''
1675       'KeepRoot'      => 1,
1676     );
1677     my $rows = $data;
1678     $rows = $rows->{$_} foreach @$xmlrow;
1679     $rows = [ $rows ] if ref($rows) ne 'ARRAY';
1680     $count = @buffer = @$rows;
1681
1682   } elsif ( $type eq 'asn.1' ) {
1683
1684     eval "use Convert::ASN1";
1685     die $@ if $@;
1686
1687     my $asn = Convert::ASN1->new;
1688     $asn->prepare( $asn_format->{'spec'} ) or die $asn->error;
1689
1690     $parser = $asn->find( $asn_format->{'macro'} ) or die $asn->error;
1691
1692     my $data = slurp($file);
1693     my $asn_output = $parser->decode( $data )
1694       or die "No ". $asn_format->{'macro'}. " found\n";
1695
1696     $asn_header_buffer = &{ $asn_format->{'header_buffer'} }( $asn_output );
1697
1698     my $rows = &{ $asn_format->{'arrayref'} }( $asn_output );
1699     $count = @buffer = @$rows;
1700
1701   } else {
1702     die "Unknown file type $type\n";
1703   }
1704
1705   #my $columns;
1706
1707   local $SIG{HUP} = 'IGNORE';
1708   local $SIG{INT} = 'IGNORE';
1709   local $SIG{QUIT} = 'IGNORE';
1710   local $SIG{TERM} = 'IGNORE';
1711   local $SIG{TSTP} = 'IGNORE';
1712   local $SIG{PIPE} = 'IGNORE';
1713
1714   my $oldAutoCommit = $FS::UID::AutoCommit;
1715   local $FS::UID::AutoCommit = 0;
1716   my $dbh = dbh;
1717
1718   #my $params  = $param->{params} || {};
1719   if ( $param->{'batch_namecol'} && $param->{'batch_namevalue'} ) {
1720     my $batch_col   = $param->{'batch_keycol'};
1721
1722     my $batch_class = 'FS::'. $param->{'batch_table'};
1723     my $batch = $batch_class->new({
1724       $param->{'batch_namecol'} => $param->{'batch_namevalue'}
1725     });
1726     my $error = $batch->insert;
1727     if ( $error ) {
1728       $dbh->rollback if $oldAutoCommit;
1729       return "can't insert batch record: $error";
1730     }
1731     #primary key via dbdef? (so the column names don't have to match)
1732     my $batch_value = $batch->get( $param->{'batch_keycol'} );
1733
1734     $params->{ $batch_col } = $batch_value;
1735   }
1736
1737   #my $job     = $param->{job};
1738   my $line;
1739   my $imported = 0;
1740   my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
1741   while (1) {
1742
1743     my @columns = ();
1744     my %hash = %$params;
1745     if ( $type eq 'csv' ) {
1746
1747       last unless scalar(@buffer);
1748       $line = shift(@buffer);
1749
1750       next if $line =~ /^\s*$/; #skip empty lines
1751
1752       $line = &{$row_callback}($line) if $row_callback;
1753       
1754       next if $line =~ /^\s*$/; #skip empty lines
1755
1756       $parser->parse($line) or do {
1757         $dbh->rollback if $oldAutoCommit;
1758         return "can't parse: ". $parser->error_input() . " " . $parser->error_diag;
1759       };
1760       @columns = $parser->fields();
1761
1762     } elsif ( $type eq 'fixedlength' ) {
1763
1764       last unless scalar(@buffer);
1765       $line = shift(@buffer);
1766
1767       @columns = $parser->parse($line);
1768
1769     } elsif ( $type eq 'xls' ) {
1770
1771       last if $row > ($parser->{MaxRow} || $parser->{MinRow})
1772            || ! $parser->{Cells}[$row];
1773
1774       my @row = @{ $parser->{Cells}[$row] };
1775       @columns = map $_->{Val}, @row;
1776
1777       #my $z = 'A';
1778       #warn $z++. ": $_\n" for @columns;
1779
1780     } elsif ( $type eq 'xml' ) {
1781
1782       # $parser = [ 'Column0Key', 'Column1Key' ... ]
1783       last unless scalar(@buffer);
1784       my $row = shift @buffer;
1785       @columns = @{ $row }{ @$parser };
1786
1787     } elsif ( $type eq 'asn.1' ) {
1788
1789       last unless scalar(@buffer);
1790       my $row = shift @buffer;
1791       foreach my $key ( keys %{ $asn_format->{map} } ) {
1792         $hash{$key} = &{ $asn_format->{map}{$key} }( $row, $asn_header_buffer );
1793       }
1794
1795     } else {
1796       die "Unknown file type $type\n";
1797     }
1798
1799     my @later = ();
1800
1801     foreach my $field ( @fields ) {
1802
1803       my $value = shift @columns;
1804      
1805       if ( ref($field) eq 'CODE' ) {
1806         #&{$field}(\%hash, $value);
1807         push @later, $field, $value;
1808       } else {
1809         #??? $hash{$field} = $value if length($value);
1810         $hash{$field} = $value if defined($value) && length($value);
1811       }
1812
1813     }
1814
1815     #my $table   = $param->{table};
1816     my $class = "FS::$table";
1817
1818     my $record = $class->new( \%hash );
1819
1820     my $param = {};
1821     while ( scalar(@later) ) {
1822       my $sub = shift @later;
1823       my $data = shift @later;
1824       eval {
1825         &{$sub}($record, $data, $conf, $param); # $record->&{$sub}($data, $conf)
1826       };
1827       if ( $@ ) {
1828         $dbh->rollback if $oldAutoCommit;
1829         return "can't insert record". ( $line ? " for $line" : '' ). ": $@";
1830       }
1831       last if exists( $param->{skiprow} );
1832     }
1833     next if exists( $param->{skiprow} );
1834
1835     if ( $preinsert_callback ) {
1836       my $error = &{$preinsert_callback}($record, $param);
1837       if ( $error ) {
1838         $dbh->rollback if $oldAutoCommit;
1839         return "preinsert_callback error". ( $line ? " for $line" : '' ).
1840                ": $error";
1841       }
1842       next if exists $param->{skiprow} && $param->{skiprow};
1843     }
1844
1845     my $error = $record->insert;
1846
1847     if ( $error ) {
1848       $dbh->rollback if $oldAutoCommit;
1849       return "can't insert record". ( $line ? " for $line" : '' ). ": $error";
1850     }
1851
1852     $row++;
1853     $imported++;
1854
1855     if ( $postinsert_callback ) {
1856       my $error = &{$postinsert_callback}($record, $param);
1857       if ( $error ) {
1858         $dbh->rollback if $oldAutoCommit;
1859         return "postinsert_callback error". ( $line ? " for $line" : '' ).
1860                ": $error";
1861       }
1862     }
1863
1864     if ( $job && time - $min_sec > $last ) { #progress bar
1865       $job->update_statustext( int(100 * $imported / $count) );
1866       $last = time;
1867     }
1868
1869   }
1870
1871   unless ( $imported || $param->{empty_ok} ) {
1872     $dbh->rollback if $oldAutoCommit;
1873     return "Empty file!";
1874   }
1875
1876   $dbh->commit or die $dbh->errstr if $oldAutoCommit;;
1877
1878   ''; #no error
1879
1880 }
1881
1882 sub _h_statement {
1883   my( $self, $action, $time ) = @_;
1884
1885   $time ||= time;
1886
1887   my %nohistory = map { $_=>1 } $self->nohistory_fields;
1888
1889   my @fields =
1890     grep { defined($self->get($_)) && $self->get($_) ne "" && ! $nohistory{$_} }
1891     real_fields($self->table);
1892   ;
1893
1894   # If we're encrypting then don't store the payinfo in the history
1895   if ( $conf && $conf->exists('encryption') && $self->table ne 'banned_pay' ) {
1896     @fields = grep { $_ ne 'payinfo' } @fields;
1897   }
1898
1899   my @values = map { _quote( $self->getfield($_), $self->table, $_) } @fields;
1900
1901   "INSERT INTO h_". $self->table. " ( ".
1902       join(', ', qw(history_date history_user history_action), @fields ).
1903     ") VALUES (".
1904       join(', ', $time, dbh->quote(getotaker()), dbh->quote($action), @values).
1905     ")"
1906   ;
1907 }
1908
1909 =item unique COLUMN
1910
1911 B<Warning>: External use is B<deprecated>.  
1912
1913 Replaces COLUMN in record with a unique number, using counters in the
1914 filesystem.  Used by the B<insert> method on single-field unique columns
1915 (see L<DBIx::DBSchema::Table>) and also as a fallback for primary keys
1916 that aren't SERIAL (Pg) or AUTO_INCREMENT (mysql).
1917
1918 Returns the new value.
1919
1920 =cut
1921
1922 sub unique {
1923   my($self,$field) = @_;
1924   my($table)=$self->table;
1925
1926   croak "Unique called on field $field, but it is ",
1927         $self->getfield($field),
1928         ", not null!"
1929     if $self->getfield($field);
1930
1931   #warn "table $table is tainted" if is_tainted($table);
1932   #warn "field $field is tainted" if is_tainted($field);
1933
1934   my($counter) = new File::CounterFile "$table.$field",0;
1935 # hack for web demo
1936 #  getotaker() =~ /^([\w\-]{1,16})$/ or die "Illegal CGI REMOTE_USER!";
1937 #  my($user)=$1;
1938 #  my($counter) = new File::CounterFile "$user/$table.$field",0;
1939 # endhack
1940
1941   my $index = $counter->inc;
1942   $index = $counter->inc while qsearchs($table, { $field=>$index } );
1943
1944   $index =~ /^(\d*)$/;
1945   $index=$1;
1946
1947   $self->setfield($field,$index);
1948
1949 }
1950
1951 =item ut_float COLUMN
1952
1953 Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May not be
1954 null.  If there is an error, returns the error, otherwise returns false.
1955
1956 =cut
1957
1958 sub ut_float {
1959   my($self,$field)=@_ ;
1960   ($self->getfield($field) =~ /^\s*(\d+\.\d+)\s*$/ ||
1961    $self->getfield($field) =~ /^\s*(\d+)\s*$/ ||
1962    $self->getfield($field) =~ /^\s*(\d+\.\d+e\d+)\s*$/ ||
1963    $self->getfield($field) =~ /^\s*(\d+e\d+)\s*$/)
1964     or return "Illegal or empty (float) $field: ". $self->getfield($field);
1965   $self->setfield($field,$1);
1966   '';
1967 }
1968 =item ut_floatn COLUMN
1969
1970 Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May be
1971 null.  If there is an error, returns the error, otherwise returns false.
1972
1973 =cut
1974
1975 #false laziness w/ut_ipn
1976 sub ut_floatn {
1977   my( $self, $field ) = @_;
1978   if ( $self->getfield($field) =~ /^()$/ ) {
1979     $self->setfield($field,'');
1980     '';
1981   } else {
1982     $self->ut_float($field);
1983   }
1984 }
1985
1986 =item ut_sfloat COLUMN
1987
1988 Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10.
1989 May not be null.  If there is an error, returns the error, otherwise returns
1990 false.
1991
1992 =cut
1993
1994 sub ut_sfloat {
1995   my($self,$field)=@_ ;
1996   ($self->getfield($field) =~ /^\s*(-?\d+\.\d+)\s*$/ ||
1997    $self->getfield($field) =~ /^\s*(-?\d+)\s*$/ ||
1998    $self->getfield($field) =~ /^\s*(-?\d+\.\d+[eE]-?\d+)\s*$/ ||
1999    $self->getfield($field) =~ /^\s*(-?\d+[eE]-?\d+)\s*$/)
2000     or return "Illegal or empty (float) $field: ". $self->getfield($field);
2001   $self->setfield($field,$1);
2002   '';
2003 }
2004 =item ut_sfloatn COLUMN
2005
2006 Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May be
2007 null.  If there is an error, returns the error, otherwise returns false.
2008
2009 =cut
2010
2011 sub ut_sfloatn {
2012   my( $self, $field ) = @_;
2013   if ( $self->getfield($field) =~ /^()$/ ) {
2014     $self->setfield($field,'');
2015     '';
2016   } else {
2017     $self->ut_sfloat($field);
2018   }
2019 }
2020
2021 =item ut_snumber COLUMN
2022
2023 Check/untaint signed numeric data (whole numbers).  If there is an error,
2024 returns the error, otherwise returns false.
2025
2026 =cut
2027
2028 sub ut_snumber {
2029   my($self, $field) = @_;
2030   $self->getfield($field) =~ /^\s*(-?)\s*(\d+)\s*$/
2031     or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
2032   $self->setfield($field, "$1$2");
2033   '';
2034 }
2035
2036 =item ut_snumbern COLUMN
2037
2038 Check/untaint signed numeric data (whole numbers).  If there is an error,
2039 returns the error, otherwise returns false.
2040
2041 =cut
2042
2043 sub ut_snumbern {
2044   my($self, $field) = @_;
2045   $self->getfield($field) =~ /^\s*(-?)\s*(\d*)\s*$/
2046     or return "Illegal (numeric) $field: ". $self->getfield($field);
2047   if ($1) {
2048     return "Illegal (numeric) $field: ". $self->getfield($field)
2049       unless $2;
2050   }
2051   $self->setfield($field, "$1$2");
2052   '';
2053 }
2054
2055 =item ut_number COLUMN
2056
2057 Check/untaint simple numeric data (whole numbers).  May not be null.  If there
2058 is an error, returns the error, otherwise returns false.
2059
2060 =cut
2061
2062 sub ut_number {
2063   my($self,$field)=@_;
2064   $self->getfield($field) =~ /^\s*(\d+)\s*$/
2065     or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
2066   $self->setfield($field,$1);
2067   '';
2068 }
2069
2070 =item ut_numbern COLUMN
2071
2072 Check/untaint simple numeric data (whole numbers).  May be null.  If there is
2073 an error, returns the error, otherwise returns false.
2074
2075 =cut
2076
2077 sub ut_numbern {
2078   my($self,$field)=@_;
2079   $self->getfield($field) =~ /^\s*(\d*)\s*$/
2080     or return "Illegal (numeric) $field: ". $self->getfield($field);
2081   $self->setfield($field,$1);
2082   '';
2083 }
2084
2085 =item ut_money COLUMN
2086
2087 Check/untaint monetary numbers.  May be negative.  Set to 0 if null.  If there
2088 is an error, returns the error, otherwise returns false.
2089
2090 =cut
2091
2092 sub ut_money {
2093   my($self,$field)=@_;
2094   $self->setfield($field, 0) if $self->getfield($field) eq '';
2095   $self->getfield($field) =~ /^\s*(\-)?\s*(\d*)(\.\d{2})?\s*$/
2096     or return "Illegal (money) $field: ". $self->getfield($field);
2097   #$self->setfield($field, "$1$2$3" || 0);
2098   $self->setfield($field, ( ($1||''). ($2||''). ($3||'') ) || 0);
2099   '';
2100 }
2101
2102 =item ut_moneyn COLUMN
2103
2104 Check/untaint monetary numbers.  May be negative.  If there
2105 is an error, returns the error, otherwise returns false.
2106
2107 =cut
2108
2109 sub ut_moneyn {
2110   my($self,$field)=@_;
2111   if ($self->getfield($field) eq '') {
2112     $self->setfield($field, '');
2113     return '';
2114   }
2115   $self->ut_money($field);
2116 }
2117
2118 =item ut_text COLUMN
2119
2120 Check/untaint text.  Alphanumerics, spaces, and the following punctuation
2121 symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < >
2122 May not be null.  If there is an error, returns the error, otherwise returns
2123 false.
2124
2125 =cut
2126
2127 sub ut_text {
2128   my($self,$field)=@_;
2129   #warn "msgcat ". \&msgcat. "\n";
2130   #warn "notexist ". \&notexist. "\n";
2131   #warn "AUTOLOAD ". \&AUTOLOAD. "\n";
2132   $self->getfield($field)
2133     =~ /^([\wô \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>$money_char]+)$/
2134       or return gettext('illegal_or_empty_text'). " $field: ".
2135                  $self->getfield($field);
2136   $self->setfield($field,$1);
2137   '';
2138 }
2139
2140 =item ut_textn COLUMN
2141
2142 Check/untaint text.  Alphanumerics, spaces, and the following punctuation
2143 symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < >
2144 May be null.  If there is an error, returns the error, otherwise returns false.
2145
2146 =cut
2147
2148 sub ut_textn {
2149   my($self,$field)=@_;
2150   return $self->setfield($field, '') if $self->getfield($field) =~ /^$/;
2151   $self->ut_text($field);
2152 }
2153
2154 =item ut_alpha COLUMN
2155
2156 Check/untaint alphanumeric strings (no spaces).  May not be null.  If there is
2157 an error, returns the error, otherwise returns false.
2158
2159 =cut
2160
2161 sub ut_alpha {
2162   my($self,$field)=@_;
2163   $self->getfield($field) =~ /^(\w+)$/
2164     or return "Illegal or empty (alphanumeric) $field: ".
2165               $self->getfield($field);
2166   $self->setfield($field,$1);
2167   '';
2168 }
2169
2170 =item ut_alphan COLUMN
2171
2172 Check/untaint alphanumeric strings (no spaces).  May be null.  If there is an
2173 error, returns the error, otherwise returns false.
2174
2175 =cut
2176
2177 sub ut_alphan {
2178   my($self,$field)=@_;
2179   $self->getfield($field) =~ /^(\w*)$/ 
2180     or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
2181   $self->setfield($field,$1);
2182   '';
2183 }
2184
2185 =item ut_alphasn COLUMN
2186
2187 Check/untaint alphanumeric strings, spaces allowed.  May be null.  If there is
2188 an error, returns the error, otherwise returns false.
2189
2190 =cut
2191
2192 sub ut_alphasn {
2193   my($self,$field)=@_;
2194   $self->getfield($field) =~ /^([\w ]*)$/ 
2195     or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
2196   $self->setfield($field,$1);
2197   '';
2198 }
2199
2200
2201 =item ut_alpha_lower COLUMN
2202
2203 Check/untaint lowercase alphanumeric strings (no spaces).  May not be null.  If
2204 there is an error, returns the error, otherwise returns false.
2205
2206 =cut
2207
2208 sub ut_alpha_lower {
2209   my($self,$field)=@_;
2210   $self->getfield($field) =~ /[[:upper:]]/
2211     and return "Uppercase characters are not permitted in $field";
2212   $self->ut_alpha($field);
2213 }
2214
2215 =item ut_phonen COLUMN [ COUNTRY ]
2216
2217 Check/untaint phone numbers.  May be null.  If there is an error, returns
2218 the error, otherwise returns false.
2219
2220 Takes an optional two-letter ISO country code; without it or with unsupported
2221 countries, ut_phonen simply calls ut_alphan.
2222
2223 =cut
2224
2225 sub ut_phonen {
2226   my( $self, $field, $country ) = @_;
2227   return $self->ut_alphan($field) unless defined $country;
2228   my $phonen = $self->getfield($field);
2229   if ( $phonen eq '' ) {
2230     $self->setfield($field,'');
2231   } elsif ( $country eq 'US' || $country eq 'CA' ) {
2232     $phonen =~ s/\D//g;
2233     $phonen = $conf->config('cust_main-default_areacode').$phonen
2234       if length($phonen)==7 && $conf->config('cust_main-default_areacode');
2235     $phonen =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/
2236       or return gettext('illegal_phone'). " $field: ". $self->getfield($field);
2237     $phonen = "$1-$2-$3";
2238     $phonen .= " x$4" if $4;
2239     $self->setfield($field,$phonen);
2240   } else {
2241     warn "warning: don't know how to check phone numbers for country $country";
2242     return $self->ut_textn($field);
2243   }
2244   '';
2245 }
2246
2247 =item ut_hex COLUMN
2248
2249 Check/untaint hexadecimal values.
2250
2251 =cut
2252
2253 sub ut_hex {
2254   my($self, $field) = @_;
2255   $self->getfield($field) =~ /^([\da-fA-F]+)$/
2256     or return "Illegal (hex) $field: ". $self->getfield($field);
2257   $self->setfield($field, uc($1));
2258   '';
2259 }
2260
2261 =item ut_hexn COLUMN
2262
2263 Check/untaint hexadecimal values.  May be null.
2264
2265 =cut
2266
2267 sub ut_hexn {
2268   my($self, $field) = @_;
2269   $self->getfield($field) =~ /^([\da-fA-F]*)$/
2270     or return "Illegal (hex) $field: ". $self->getfield($field);
2271   $self->setfield($field, uc($1));
2272   '';
2273 }
2274
2275 =item ut_mac_addr COLUMN
2276
2277 Check/untaint mac addresses.  May be null.
2278
2279 =cut
2280
2281 sub ut_mac_addr {
2282   my($self, $field) = @_;
2283
2284   my $mac = $self->get($field);
2285   $mac =~ s/\s+//g;
2286   $mac =~ s/://g;
2287   $self->set($field, $mac);
2288
2289   my $e = $self->ut_hex($field);
2290   return $e if $e;
2291
2292   return "Illegal (mac address) $field: ". $self->getfield($field)
2293     unless length($self->getfield($field)) == 12;
2294
2295   '';
2296
2297 }
2298
2299 =item ut_mac_addrn COLUMN
2300
2301 Check/untaint mac addresses.  May be null.
2302
2303 =cut
2304
2305 sub ut_mac_addrn {
2306   my($self, $field) = @_;
2307   ($self->getfield($field) eq '') ? '' : $self->ut_mac_addr($field);
2308 }
2309
2310 =item ut_ip COLUMN
2311
2312 Check/untaint ip addresses.  IPv4 only for now, though ::1 is auto-translated
2313 to 127.0.0.1.
2314
2315 =cut
2316
2317 sub ut_ip {
2318   my( $self, $field ) = @_;
2319   $self->setfield($field, '127.0.0.1') if $self->getfield($field) eq '::1';
2320   $self->getfield($field) =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
2321     or return "Illegal (IP address) $field: ". $self->getfield($field);
2322   for ( $1, $2, $3, $4 ) { return "Illegal (IP address) $field" if $_ > 255; }
2323   $self->setfield($field, "$1.$2.$3.$4");
2324   '';
2325 }
2326
2327 =item ut_ipn COLUMN
2328
2329 Check/untaint ip addresses.  IPv4 only for now, though ::1 is auto-translated
2330 to 127.0.0.1.  May be null.
2331
2332 =cut
2333
2334 sub ut_ipn {
2335   my( $self, $field ) = @_;
2336   if ( $self->getfield($field) =~ /^()$/ ) {
2337     $self->setfield($field,'');
2338     '';
2339   } else {
2340     $self->ut_ip($field);
2341   }
2342 }
2343
2344 =item ut_ip46 COLUMN
2345
2346 Check/untaint IPv4 or IPv6 address.
2347
2348 =cut
2349
2350 sub ut_ip46 {
2351   my( $self, $field ) = @_;
2352   my $ip = NetAddr::IP->new($self->getfield($field))
2353     or return "Illegal (IP address) $field: ".$self->getfield($field);
2354   $self->setfield($field, lc($ip->addr));
2355   return '';
2356 }
2357
2358 =item ut_ip46n
2359
2360 Check/untaint IPv6 or IPv6 address.  May be null.
2361
2362 =cut
2363
2364 sub ut_ip46n {
2365   my( $self, $field ) = @_;
2366   if ( $self->getfield($field) =~ /^$/ ) {
2367     $self->setfield($field, '');
2368     return '';
2369   }
2370   $self->ut_ip46($field);
2371 }
2372
2373 =item ut_coord COLUMN [ LOWER [ UPPER ] ]
2374
2375 Check/untaint coordinates.
2376 Accepts the following forms:
2377 DDD.DDDDD
2378 -DDD.DDDDD
2379 DDD MM.MMM
2380 -DDD MM.MMM
2381 DDD MM SS
2382 -DDD MM SS
2383 DDD MM MMM
2384 -DDD MM MMM
2385
2386 The "DDD MM SS" and "DDD MM MMM" are potentially ambiguous.
2387 The latter form (that is, the MMM are thousands of minutes) is
2388 assumed if the "MMM" is exactly three digits or two digits > 59.
2389
2390 To be safe, just use the DDD.DDDDD form.
2391
2392 If LOWER or UPPER are specified, then the coordinate is checked
2393 for lower and upper bounds, respectively.
2394
2395 =cut
2396
2397 sub ut_coord {
2398   my ($self, $field) = (shift, shift);
2399
2400   my($lower, $upper);
2401   if ( $field =~ /latitude/ ) {
2402     $lower = $lat_lower;
2403     $upper = 90;
2404   } elsif ( $field =~ /longitude/ ) {
2405     $lower = -180;
2406     $upper = $lon_upper;
2407   }
2408
2409   my $coord = $self->getfield($field);
2410   my $neg = $coord =~ s/^(-)//;
2411
2412   my ($d, $m, $s) = (0, 0, 0);
2413
2414   if (
2415     (($d) = ($coord =~ /^(\s*\d{1,3}(?:\.\d+)?)\s*$/)) ||
2416     (($d, $m) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2}(?:\.\d+))\s*$/)) ||
2417     (($d, $m, $s) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2})\s+(\d{1,3})\s*$/))
2418   ) {
2419     $s = (((($s =~ /^\d{3}$/) or $s > 59) ? ($s / 1000) : ($s / 60)) / 60);
2420     $m = $m / 60;
2421     if ($m > 59) {
2422       return "Invalid (coordinate with minutes > 59) $field: "
2423              . $self->getfield($field);
2424     }
2425
2426     $coord = ($neg ? -1 : 1) * sprintf('%.8f', $d + $m + $s);
2427
2428     if (defined($lower) and ($coord < $lower)) {
2429       return "Invalid (coordinate < $lower) $field: "
2430              . $self->getfield($field);;
2431     }
2432
2433     if (defined($upper) and ($coord > $upper)) {
2434       return "Invalid (coordinate > $upper) $field: "
2435              . $self->getfield($field);;
2436     }
2437
2438     $self->setfield($field, $coord);
2439     return '';
2440   }
2441
2442   return "Invalid (coordinate) $field: " . $self->getfield($field);
2443
2444 }
2445
2446 =item ut_coordn COLUMN [ LOWER [ UPPER ] ]
2447
2448 Same as ut_coord, except optionally null.
2449
2450 =cut
2451
2452 sub ut_coordn {
2453
2454   my ($self, $field) = (shift, shift);
2455
2456   if ($self->getfield($field) =~ /^\s*$/) {
2457     return '';
2458   } else {
2459     return $self->ut_coord($field, @_);
2460   }
2461
2462 }
2463
2464 =item ut_domain COLUMN
2465
2466 Check/untaint host and domain names.  May not be null.
2467
2468 =cut
2469
2470 sub ut_domain {
2471   my( $self, $field ) = @_;
2472   #$self->getfield($field) =~/^(\w+\.)*\w+$/
2473   $self->getfield($field) =~/^(([\w\-]+\.)*\w+)$/
2474     or return "Illegal (hostname) $field: ". $self->getfield($field);
2475   $self->setfield($field,$1);
2476   '';
2477 }
2478
2479 =item ut_domainn COLUMN
2480
2481 Check/untaint host and domain names.  May be null.
2482
2483 =cut
2484
2485 sub ut_domainn {
2486   my( $self, $field ) = @_;
2487   if ( $self->getfield($field) =~ /^()$/ ) {
2488     $self->setfield($field,'');
2489     '';
2490   } else {
2491     $self->ut_domain($field);
2492   }
2493 }
2494
2495 =item ut_name COLUMN
2496
2497 Check/untaint proper names; allows alphanumerics, spaces and the following
2498 punctuation: , . - '
2499
2500 May not be null.
2501
2502 =cut
2503
2504 sub ut_name {
2505   my( $self, $field ) = @_;
2506 #  warn "ut_name allowed alphanumerics: +(sort grep /\w/, map { chr() } 0..255), "\n";
2507   $self->getfield($field) =~ /^([\w \,\.\-\']+)$/
2508     or return gettext('illegal_name'). " $field: ". $self->getfield($field);
2509   $self->setfield($field,$1);
2510   '';
2511 }
2512
2513 =item ut_zip COLUMN
2514
2515 Check/untaint zip codes.
2516
2517 =cut
2518
2519 my @zip_reqd_countries = qw( AU CA US ); #CA, US implicit...
2520
2521 sub ut_zip {
2522   my( $self, $field, $country ) = @_;
2523
2524   if ( $country eq 'US' ) {
2525
2526     $self->getfield($field) =~ /^\s*(\d{5}(\-\d{4})?)\s*$/
2527       or return gettext('illegal_zip'). " $field for country $country: ".
2528                 $self->getfield($field);
2529     $self->setfield($field, $1);
2530
2531   } elsif ( $country eq 'CA' ) {
2532
2533     $self->getfield($field) =~ /^\s*([A-Z]\d[A-Z])\s*(\d[A-Z]\d)\s*$/i
2534       or return gettext('illegal_zip'). " $field for country $country: ".
2535                 $self->getfield($field);
2536     $self->setfield($field, "$1 $2");
2537
2538   } else {
2539
2540     if ( $self->getfield($field) =~ /^\s*$/
2541          && ( !$country || ! grep { $_ eq $country } @zip_reqd_countries )
2542        )
2543     {
2544       $self->setfield($field,'');
2545     } else {
2546       $self->getfield($field) =~ /^\s*(\w[\w\-\s]{0,8}\w)\s*$/
2547         or return gettext('illegal_zip'). " $field: ". $self->getfield($field);
2548       $self->setfield($field,$1);
2549     }
2550
2551   }
2552
2553   '';
2554 }
2555
2556 =item ut_country COLUMN
2557
2558 Check/untaint country codes.  Country names are changed to codes, if possible -
2559 see L<Locale::Country>.
2560
2561 =cut
2562
2563 sub ut_country {
2564   my( $self, $field ) = @_;
2565   unless ( $self->getfield($field) =~ /^(\w\w)$/ ) {
2566     if ( $self->getfield($field) =~ /^([\w \,\.\(\)\']+)$/ 
2567          && country2code($1) ) {
2568       $self->setfield($field,uc(country2code($1)));
2569     }
2570   }
2571   $self->getfield($field) =~ /^(\w\w)$/
2572     or return "Illegal (country) $field: ". $self->getfield($field);
2573   $self->setfield($field,uc($1));
2574   '';
2575 }
2576
2577 =item ut_anything COLUMN
2578
2579 Untaints arbitrary data.  Be careful.
2580
2581 =cut
2582
2583 sub ut_anything {
2584   my( $self, $field ) = @_;
2585   $self->getfield($field) =~ /^(.*)$/s
2586     or return "Illegal $field: ". $self->getfield($field);
2587   $self->setfield($field,$1);
2588   '';
2589 }
2590
2591 =item ut_enum COLUMN CHOICES_ARRAYREF
2592
2593 Check/untaint a column, supplying all possible choices, like the "enum" type.
2594
2595 =cut
2596
2597 sub ut_enum {
2598   my( $self, $field, $choices ) = @_;
2599   foreach my $choice ( @$choices ) {
2600     if ( $self->getfield($field) eq $choice ) {
2601       $self->setfield($field, $choice);
2602       return '';
2603     }
2604   }
2605   return "Illegal (enum) field $field: ". $self->getfield($field);
2606 }
2607
2608 =item ut_enumn COLUMN CHOICES_ARRAYREF
2609
2610 Like ut_enum, except the null value is also allowed.
2611
2612 =cut
2613
2614 sub ut_enumn {
2615   my( $self, $field, $choices ) = @_;
2616   $self->getfield($field)
2617     ? $self->ut_enum($field, $choices)
2618     : '';
2619 }
2620
2621 =item ut_flag COLUMN
2622
2623 Check/untaint a column if it contains either an empty string or 'Y'.  This
2624 is the standard form for boolean flags in Freeside.
2625
2626 =cut
2627
2628 sub ut_flag {
2629   my( $self, $field ) = @_;
2630   my $value = uc($self->getfield($field));
2631   if ( $value eq '' or $value eq 'Y' ) {
2632     $self->setfield($field, $value);
2633     return '';
2634   }
2635   return "Illegal (flag) field $field: $value";
2636 }
2637
2638 =item ut_foreign_key COLUMN FOREIGN_TABLE FOREIGN_COLUMN
2639
2640 Check/untaint a foreign column key.  Call a regular ut_ method (like ut_number)
2641 on the column first.
2642
2643 =cut
2644
2645 sub ut_foreign_key {
2646   my( $self, $field, $table, $foreign ) = @_;
2647   return '' if $no_check_foreign;
2648   qsearchs($table, { $foreign => $self->getfield($field) })
2649     or return "Can't find ". $self->table. ".$field ". $self->getfield($field).
2650               " in $table.$foreign";
2651   '';
2652 }
2653
2654 =item ut_foreign_keyn COLUMN FOREIGN_TABLE FOREIGN_COLUMN
2655
2656 Like ut_foreign_key, except the null value is also allowed.
2657
2658 =cut
2659
2660 sub ut_foreign_keyn {
2661   my( $self, $field, $table, $foreign ) = @_;
2662   $self->getfield($field)
2663     ? $self->ut_foreign_key($field, $table, $foreign)
2664     : '';
2665 }
2666
2667 =item ut_agentnum_acl COLUMN [ NULL_RIGHT | NULL_RIGHT_LISTREF ]
2668
2669 Checks this column as an agentnum, taking into account the current users's
2670 ACLs.  NULL_RIGHT or NULL_RIGHT_LISTREF, if specified, indicates the access
2671 right or rights allowing no agentnum.
2672
2673 =cut
2674
2675 sub ut_agentnum_acl {
2676   my( $self, $field ) = (shift, shift);
2677   my $null_acl = scalar(@_) ? shift : [];
2678   $null_acl = [ $null_acl ] unless ref($null_acl);
2679
2680   my $error = $self->ut_foreign_keyn($field, 'agent', 'agentnum');
2681   return "Illegal agentnum: $error" if $error;
2682
2683   my $curuser = $FS::CurrentUser::CurrentUser;
2684
2685   if ( $self->$field() ) {
2686
2687     return "Access denied"
2688       unless $curuser->agentnum($self->$field());
2689
2690   } else {
2691
2692     return "Access denied"
2693       unless grep $curuser->access_right($_), @$null_acl;
2694
2695   }
2696
2697   '';
2698
2699 }
2700
2701 =item fields [ TABLE ]
2702
2703 This is a wrapper for real_fields.  Code that called
2704 fields before should probably continue to call fields.
2705
2706 =cut
2707
2708 sub fields {
2709   my $something = shift;
2710   my $table;
2711   if($something->isa('FS::Record')) {
2712     $table = $something->table;
2713   } else {
2714     $table = $something;
2715     $something = "FS::$table";
2716   }
2717   return (real_fields($table));
2718 }
2719
2720
2721 =item encrypt($value)
2722
2723 Encrypts the credit card using a combination of PK to encrypt and uuencode to armour.
2724
2725 Returns the encrypted string.
2726
2727 You should generally not have to worry about calling this, as the system handles this for you.
2728
2729 =cut
2730
2731 sub encrypt {
2732   my ($self, $value) = @_;
2733   my $encrypted;
2734
2735   if ($conf->exists('encryption')) {
2736     if ($self->is_encrypted($value)) {
2737       # Return the original value if it isn't plaintext.
2738       $encrypted = $value;
2739     } else {
2740       $self->loadRSA;
2741       if (ref($rsa_encrypt) =~ /::RSA/) { # We Can Encrypt
2742         # RSA doesn't like the empty string so let's pack it up
2743         # The database doesn't like the RSA data so uuencode it
2744         my $length = length($value)+1;
2745         $encrypted = pack("u*",$rsa_encrypt->encrypt(pack("Z$length",$value)));
2746       } else {
2747         die ("You can't encrypt w/o a valid RSA engine - Check your installation or disable encryption");
2748       }
2749     }
2750   }
2751   return $encrypted;
2752 }
2753
2754 =item is_encrypted($value)
2755
2756 Checks to see if the string is encrypted and returns true or false (1/0) to indicate it's status.
2757
2758 =cut
2759
2760
2761 sub is_encrypted {
2762   my ($self, $value) = @_;
2763   # Possible Bug - Some work may be required here....
2764
2765   if ($value =~ /^M/ && length($value) > 80) {
2766     return 1;
2767   } else {
2768     return 0;
2769   }
2770 }
2771
2772 =item decrypt($value)
2773
2774 Uses the private key to decrypt the string. Returns the decryoted string or undef on failure.
2775
2776 You should generally not have to worry about calling this, as the system handles this for you.
2777
2778 =cut
2779
2780 sub decrypt {
2781   my ($self,$value) = @_;
2782   my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
2783   if ($conf->exists('encryption') && $self->is_encrypted($value)) {
2784     $self->loadRSA;
2785     if (ref($rsa_decrypt) =~ /::RSA/) {
2786       my $encrypted = unpack ("u*", $value);
2787       $decrypted =  unpack("Z*", eval{$rsa_decrypt->decrypt($encrypted)});
2788       if ($@) {warn "Decryption Failed"};
2789     }
2790   }
2791   return $decrypted;
2792 }
2793
2794 sub loadRSA {
2795     my $self = shift;
2796     #Initialize the Module
2797     $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default
2798
2799     if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') {
2800       $rsa_module = $conf->config('encryptionmodule');
2801     }
2802
2803     if (!$rsa_loaded) {
2804         eval ("require $rsa_module"); # No need to import the namespace
2805         $rsa_loaded++;
2806     }
2807     # Initialize Encryption
2808     if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') {
2809       my $public_key = join("\n",$conf->config('encryptionpublickey'));
2810       $rsa_encrypt = $rsa_module->new_public_key($public_key);
2811     }
2812     
2813     # Intitalize Decryption
2814     if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') {
2815       my $private_key = join("\n",$conf->config('encryptionprivatekey'));
2816       $rsa_decrypt = $rsa_module->new_private_key($private_key);
2817     }
2818 }
2819
2820 =item h_search ACTION
2821
2822 Given an ACTION, either "insert", or "delete", returns the appropriate history
2823 record corresponding to this record, if any.
2824
2825 =cut
2826
2827 sub h_search {
2828   my( $self, $action ) = @_;
2829
2830   my $table = $self->table;
2831   $table =~ s/^h_//;
2832
2833   my $primary_key = dbdef->table($table)->primary_key;
2834
2835   qsearchs({
2836     'table'   => "h_$table",
2837     'hashref' => { $primary_key     => $self->$primary_key(),
2838                    'history_action' => $action,
2839                  },
2840   });
2841
2842 }
2843
2844 =item h_date ACTION
2845
2846 Given an ACTION, either "insert", or "delete", returns the timestamp of the
2847 appropriate history record corresponding to this record, if any.
2848
2849 =cut
2850
2851 sub h_date {
2852   my($self, $action) = @_;
2853   my $h = $self->h_search($action);
2854   $h ? $h->history_date : '';
2855 }
2856
2857 =item scalar_sql SQL [ PLACEHOLDER, ... ]
2858
2859 A class or object method.  Executes the sql statement represented by SQL and
2860 returns a scalar representing the result: the first column of the first row.
2861
2862 Dies on bogus SQL.  Returns an empty string if no row is returned.
2863
2864 Typically used for statments which return a single value such as "SELECT
2865 COUNT(*) FROM table WHERE something" OR "SELECT column FROM table WHERE key = ?"
2866
2867 =cut
2868
2869 sub scalar_sql {
2870   my($self, $sql) = (shift, shift);
2871   my $sth = dbh->prepare($sql) or die dbh->errstr;
2872   $sth->execute(@_)
2873     or die "Unexpected error executing statement $sql: ". $sth->errstr;
2874   my $row = $sth->fetchrow_arrayref or return '';
2875   my $scalar = $row->[0];
2876   defined($scalar) ? $scalar : '';
2877 }
2878
2879 =item count [ WHERE ]
2880
2881 Convenience method for the common case of "SELECT COUNT(*) FROM table", 
2882 with optional WHERE.  Must be called as method on a class with an 
2883 associated table.
2884
2885 =cut
2886
2887 sub count {
2888   my($self, $where) = (shift, shift);
2889   my $table = $self->table or die 'count called on object of class '.ref($self);
2890   my $sql = "SELECT COUNT(*) FROM $table";
2891   $sql .= " WHERE $where" if $where;
2892   $self->scalar_sql($sql);
2893 }
2894
2895 =back
2896
2897 =head1 SUBROUTINES
2898
2899 =over 4
2900
2901 =item real_fields [ TABLE ]
2902
2903 Returns a list of the real columns in the specified table.  Called only by 
2904 fields() and other subroutines elsewhere in FS::Record.
2905
2906 =cut
2907
2908 sub real_fields {
2909   my $table = shift;
2910
2911   my($table_obj) = dbdef->table($table);
2912   confess "Unknown table $table" unless $table_obj;
2913   $table_obj->columns;
2914 }
2915
2916 =item pvf FIELD_NAME
2917
2918 Returns the FS::part_virtual_field object corresponding to a field in the 
2919 record (specified by FIELD_NAME).
2920
2921 =cut
2922
2923 sub pvf {
2924   my ($self, $name) = (shift, shift);
2925
2926   if(grep /^$name$/, $self->virtual_fields) {
2927     $name =~ s/^cf_//;
2928     my $concat = [ "'cf_'", "name" ];
2929     return qsearchs({   table   =>  'part_virtual_field',
2930                         hashref =>  { dbtable => $self->table,
2931                                       name    => $name 
2932                                     },
2933                         select  =>  'vfieldpart, dbtable, length, label, '.concat_sql($concat).' as name',
2934                     });
2935   }
2936   ''
2937 }
2938
2939 =item _quote VALUE, TABLE, COLUMN
2940
2941 This is an internal function used to construct SQL statements.  It returns
2942 VALUE DBI-quoted (see L<DBI/"quote">) unless VALUE is a number and the column
2943 type (see L<DBIx::DBSchema::Column>) does not end in `char' or `binary'.
2944
2945 =cut
2946
2947 sub _quote {
2948   my($value, $table, $column) = @_;
2949   my $column_obj = dbdef->table($table)->column($column);
2950   my $column_type = $column_obj->type;
2951   my $nullable = $column_obj->null;
2952
2953   warn "  $table.$column: $value ($column_type".
2954        ( $nullable ? ' NULL' : ' NOT NULL' ).
2955        ")\n" if $DEBUG > 2;
2956
2957   if ( $value eq '' && $nullable ) {
2958     'NULL';
2959   } elsif ( $value eq '' && $column_type =~ /^(int|numeric)/ ) {
2960     cluck "WARNING: Attempting to set non-null integer $table.$column null; ".
2961           "using 0 instead";
2962     0;
2963   } elsif ( $value =~ /^\d+(\.\d+)?$/ && 
2964             ! $column_type =~ /(char|binary|text)$/i ) {
2965     $value;
2966   } elsif (( $column_type =~ /^bytea$/i || $column_type =~ /(blob|varbinary)/i )
2967            && driver_name eq 'Pg'
2968           )
2969   {
2970     no strict 'subs';
2971 #    dbh->quote($value, { pg_type => PG_BYTEA() }); # doesn't work right
2972     # Pg binary string quoting: convert each character to 3-digit octal prefixed with \\, 
2973     # single-quote the whole mess, and put an "E" in front.
2974     return ("E'" . join('', map { sprintf('\\\\%03o', ord($_)) } split(//, $value) ) . "'");
2975   } else {
2976     dbh->quote($value);
2977   }
2978 }
2979
2980 =item hfields TABLE
2981
2982 This is deprecated.  Don't use it.
2983
2984 It returns a hash-type list with the fields of this record's table set true.
2985
2986 =cut
2987
2988 sub hfields {
2989   carp "warning: hfields is deprecated";
2990   my($table)=@_;
2991   my(%hash);
2992   foreach (fields($table)) {
2993     $hash{$_}=1;
2994   }
2995   \%hash;
2996 }
2997
2998 sub _dump {
2999   my($self)=@_;
3000   join("\n", map {
3001     "$_: ". $self->getfield($_). "|"
3002   } (fields($self->table)) );
3003 }
3004
3005 sub DESTROY { return; }
3006
3007 #sub DESTROY {
3008 #  my $self = shift;
3009 #  #use Carp qw(cluck);
3010 #  #cluck "DESTROYING $self";
3011 #  warn "DESTROYING $self";
3012 #}
3013
3014 #sub is_tainted {
3015 #             return ! eval { join('',@_), kill 0; 1; };
3016 #         }
3017
3018 =item str2time_sql [ DRIVER_NAME ]
3019
3020 Returns a function to convert to unix time based on database type, such as
3021 "EXTRACT( EPOCH FROM" for Pg or "UNIX_TIMESTAMP(" for mysql.  See
3022 the str2time_sql_closing method to return a closing string rather than just
3023 using a closing parenthesis as previously suggested.
3024
3025 You can pass an optional driver name such as "Pg", "mysql" or
3026 $dbh->{Driver}->{Name} to return a function for that database instead of
3027 the current database.
3028
3029 =cut
3030
3031 sub str2time_sql { 
3032   my $driver = shift || driver_name;
3033
3034   return 'UNIX_TIMESTAMP('      if $driver =~ /^mysql/i;
3035   return 'EXTRACT( EPOCH FROM ' if $driver =~ /^Pg/i;
3036
3037   warn "warning: unknown database type $driver; guessing how to convert ".
3038        "dates to UNIX timestamps";
3039   return 'EXTRACT(EPOCH FROM ';
3040
3041 }
3042
3043 =item str2time_sql_closing [ DRIVER_NAME ]
3044
3045 Returns the closing suffix of a function to convert to unix time based on
3046 database type, such as ")::integer" for Pg or ")" for mysql.
3047
3048 You can pass an optional driver name such as "Pg", "mysql" or
3049 $dbh->{Driver}->{Name} to return a function for that database instead of
3050 the current database.
3051
3052 =cut
3053
3054 sub str2time_sql_closing { 
3055   my $driver = shift || driver_name;
3056
3057   return ' )::INTEGER ' if $driver =~ /^Pg/i;
3058   return ' ) ';
3059 }
3060
3061 =item regexp_sql [ DRIVER_NAME ]
3062
3063 Returns the operator to do a regular expression comparison based on database
3064 type, such as '~' for Pg or 'REGEXP' for mysql.
3065
3066 You can pass an optional driver name such as "Pg", "mysql" or
3067 $dbh->{Driver}->{Name} to return a function for that database instead of
3068 the current database.
3069
3070 =cut
3071
3072 sub regexp_sql {
3073   my $driver = shift || driver_name;
3074
3075   return '~'      if $driver =~ /^Pg/i;
3076   return 'REGEXP' if $driver =~ /^mysql/i;
3077
3078   die "don't know how to use regular expressions in ". driver_name." databases";
3079
3080 }
3081
3082 =item not_regexp_sql [ DRIVER_NAME ]
3083
3084 Returns the operator to do a regular expression negation based on database
3085 type, such as '!~' for Pg or 'NOT REGEXP' for mysql.
3086
3087 You can pass an optional driver name such as "Pg", "mysql" or
3088 $dbh->{Driver}->{Name} to return a function for that database instead of
3089 the current database.
3090
3091 =cut
3092
3093 sub not_regexp_sql {
3094   my $driver = shift || driver_name;
3095
3096   return '!~'         if $driver =~ /^Pg/i;
3097   return 'NOT REGEXP' if $driver =~ /^mysql/i;
3098
3099   die "don't know how to use regular expressions in ". driver_name." databases";
3100
3101 }
3102
3103 =item concat_sql [ DRIVER_NAME ] ITEMS_ARRAYREF
3104
3105 Returns the items concatenated based on database type, using "CONCAT()" for
3106 mysql and " || " for Pg and other databases.
3107
3108 You can pass an optional driver name such as "Pg", "mysql" or
3109 $dbh->{Driver}->{Name} to return a function for that database instead of
3110 the current database.
3111
3112 =cut
3113
3114 sub concat_sql {
3115   my $driver = ref($_[0]) ? driver_name : shift;
3116   my $items = shift;
3117
3118   if ( $driver =~ /^mysql/i ) {
3119     'CONCAT('. join(',', @$items). ')';
3120   } else {
3121     join('||', @$items);
3122   }
3123
3124 }
3125
3126 =item midnight_sql DATE
3127
3128 Returns an SQL expression to convert DATE (a unix timestamp) to midnight 
3129 on that day in the system timezone, using the default driver name.
3130
3131 =cut
3132
3133 sub midnight_sql {
3134   my $driver = driver_name;
3135   my $expr = shift;
3136   if ( $driver =~ /^mysql/i ) {
3137     "UNIX_TIMESTAMP(DATE(FROM_UNIXTIME($expr)))";
3138   }
3139   else {
3140     "EXTRACT( EPOCH FROM DATE(TO_TIMESTAMP($expr)) )";
3141   }
3142 }
3143
3144 =back
3145
3146 =head1 BUGS
3147
3148 This module should probably be renamed, since much of the functionality is
3149 of general use.  It is not completely unlike Adapter::DBI (see below).
3150
3151 Exported qsearch and qsearchs should be deprecated in favor of method calls
3152 (against an FS::Record object like the old search and searchs that qsearch
3153 and qsearchs were on top of.)
3154
3155 The whole fields / hfields mess should be removed.
3156
3157 The various WHERE clauses should be subroutined.
3158
3159 table string should be deprecated in favor of DBIx::DBSchema::Table.
3160
3161 No doubt we could benefit from a Tied hash.  Documenting how exists / defined
3162 true maps to the database (and WHERE clauses) would also help.
3163
3164 The ut_ methods should ask the dbdef for a default length.
3165
3166 ut_sqltype (like ut_varchar) should all be defined
3167
3168 A fallback check method should be provided which uses the dbdef.
3169
3170 The ut_money method assumes money has two decimal digits.
3171
3172 The Pg money kludge in the new method only strips `$'.
3173
3174 The ut_phonen method only checks US-style phone numbers.
3175
3176 The _quote function should probably use ut_float instead of a regex.
3177
3178 All the subroutines probably should be methods, here or elsewhere.
3179
3180 Probably should borrow/use some dbdef methods where appropriate (like sub
3181 fields)
3182
3183 As of 1.14, DBI fetchall_hashref( {} ) doesn't set fetchrow_hashref NAME_lc,
3184 or allow it to be set.  Working around it is ugly any way around - DBI should
3185 be fixed.  (only affects RDBMS which return uppercase column names)
3186
3187 ut_zip should take an optional country like ut_phone.
3188
3189 =head1 SEE ALSO
3190
3191 L<DBIx::DBSchema>, L<FS::UID>, L<DBI>
3192
3193 Adapter::DBI from Ch. 11 of Advanced Perl Programming by Sriram Srinivasan.
3194
3195 http://poop.sf.net/
3196
3197 =cut
3198
3199 1;
3200