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