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