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