tokyo fix, RT#12981
[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
2389 =item ut_mac_addr COLUMN
2390
2391 Check/untaint mac addresses.  May be null.
2392
2393 =cut
2394
2395 sub ut_mac_addr {
2396   my($self, $field) = @_;
2397
2398   my $mac = $self->get($field);
2399   $mac =~ s/\s+//g;
2400   $mac =~ s/://g;
2401   $self->set($field, $mac);
2402
2403   my $e = $self->ut_hex($field);
2404   return $e if $e;
2405
2406   return "Illegal (mac address) $field: ". $self->getfield($field)
2407     unless length($self->getfield($field)) == 12;
2408
2409   '';
2410
2411 }
2412
2413 =item ut_mac_addrn COLUMN
2414
2415 Check/untaint mac addresses.  May be null.
2416
2417 =cut
2418
2419 sub ut_mac_addrn {
2420   my($self, $field) = @_;
2421   ($self->getfield($field) eq '') ? '' : $self->ut_mac_addr($field);
2422 }
2423
2424 =item ut_ip COLUMN
2425
2426 Check/untaint ip addresses.  IPv4 only for now, though ::1 is auto-translated
2427 to 127.0.0.1.
2428
2429 =cut
2430
2431 sub ut_ip {
2432   my( $self, $field ) = @_;
2433   $self->setfield($field, '127.0.0.1') if $self->getfield($field) eq '::1';
2434   $self->getfield($field) =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
2435     or return "Illegal (IP address) $field: ". $self->getfield($field);
2436   for ( $1, $2, $3, $4 ) { return "Illegal (IP address) $field" if $_ > 255; }
2437   $self->setfield($field, "$1.$2.$3.$4");
2438   '';
2439 }
2440
2441 =item ut_ipn COLUMN
2442
2443 Check/untaint ip addresses.  IPv4 only for now, though ::1 is auto-translated
2444 to 127.0.0.1.  May be null.
2445
2446 =cut
2447
2448 sub ut_ipn {
2449   my( $self, $field ) = @_;
2450   if ( $self->getfield($field) =~ /^()$/ ) {
2451     $self->setfield($field,'');
2452     '';
2453   } else {
2454     $self->ut_ip($field);
2455   }
2456 }
2457
2458 =item ut_ip46 COLUMN
2459
2460 Check/untaint IPv4 or IPv6 address.
2461
2462 =cut
2463
2464 sub ut_ip46 {
2465   my( $self, $field ) = @_;
2466   my $ip = NetAddr::IP->new($self->getfield($field))
2467     or return "Illegal (IP address) $field: ".$self->getfield($field);
2468   $self->setfield($field, lc($ip->addr));
2469   return '';
2470 }
2471
2472 =item ut_ip46n
2473
2474 Check/untaint IPv6 or IPv6 address.  May be null.
2475
2476 =cut
2477
2478 sub ut_ip46n {
2479   my( $self, $field ) = @_;
2480   if ( $self->getfield($field) =~ /^$/ ) {
2481     $self->setfield($field, '');
2482     return '';
2483   }
2484   $self->ut_ip46($field);
2485 }
2486
2487 =item ut_coord COLUMN [ LOWER [ UPPER ] ]
2488
2489 Check/untaint coordinates.
2490 Accepts the following forms:
2491 DDD.DDDDD
2492 -DDD.DDDDD
2493 DDD MM.MMM
2494 -DDD MM.MMM
2495 DDD MM SS
2496 -DDD MM SS
2497 DDD MM MMM
2498 -DDD MM MMM
2499
2500 The "DDD MM SS" and "DDD MM MMM" are potentially ambiguous.
2501 The latter form (that is, the MMM are thousands of minutes) is
2502 assumed if the "MMM" is exactly three digits or two digits > 59.
2503
2504 To be safe, just use the DDD.DDDDD form.
2505
2506 If LOWER or UPPER are specified, then the coordinate is checked
2507 for lower and upper bounds, respectively.
2508
2509 =cut
2510
2511 sub ut_coord {
2512
2513   my ($self, $field) = (shift, shift);
2514
2515   my $lower = shift if scalar(@_);
2516   my $upper = shift if scalar(@_);
2517   my $coord = $self->getfield($field);
2518   my $neg = $coord =~ s/^(-)//;
2519
2520   my ($d, $m, $s) = (0, 0, 0);
2521
2522   if (
2523     (($d) = ($coord =~ /^(\s*\d{1,3}(?:\.\d+)?)\s*$/)) ||
2524     (($d, $m) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2}(?:\.\d+))\s*$/)) ||
2525     (($d, $m, $s) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2})\s+(\d{1,3})\s*$/))
2526   ) {
2527     $s = (((($s =~ /^\d{3}$/) or $s > 59) ? ($s / 1000) : ($s / 60)) / 60);
2528     $m = $m / 60;
2529     if ($m > 59) {
2530       return "Invalid (coordinate with minutes > 59) $field: "
2531              . $self->getfield($field);
2532     }
2533
2534     $coord = ($neg ? -1 : 1) * sprintf('%.8f', $d + $m + $s);
2535
2536     if (defined($lower) and ($coord < $lower)) {
2537       return "Invalid (coordinate < $lower) $field: "
2538              . $self->getfield($field);;
2539     }
2540
2541     if (defined($upper) and ($coord > $upper)) {
2542       return "Invalid (coordinate > $upper) $field: "
2543              . $self->getfield($field);;
2544     }
2545
2546     $self->setfield($field, $coord);
2547     return '';
2548   }
2549
2550   return "Invalid (coordinate) $field: " . $self->getfield($field);
2551
2552 }
2553
2554 =item ut_coordn COLUMN [ LOWER [ UPPER ] ]
2555
2556 Same as ut_coord, except optionally null.
2557
2558 =cut
2559
2560 sub ut_coordn {
2561
2562   my ($self, $field) = (shift, shift);
2563
2564   if ($self->getfield($field) =~ /^$/) {
2565     return '';
2566   } else {
2567     return $self->ut_coord($field, @_);
2568   }
2569
2570 }
2571
2572
2573 =item ut_domain COLUMN
2574
2575 Check/untaint host and domain names.
2576
2577 =cut
2578
2579 sub ut_domain {
2580   my( $self, $field ) = @_;
2581   #$self->getfield($field) =~/^(\w+\.)*\w+$/
2582   $self->getfield($field) =~/^(([\w\-]+\.)*\w+)$/
2583     or return "Illegal (domain) $field: ". $self->getfield($field);
2584   $self->setfield($field,$1);
2585   '';
2586 }
2587
2588 =item ut_name COLUMN
2589
2590 Check/untaint proper names; allows alphanumerics, spaces and the following
2591 punctuation: , . - '
2592
2593 May not be null.
2594
2595 =cut
2596
2597 sub ut_name {
2598   my( $self, $field ) = @_;
2599 #  warn "ut_name allowed alphanumerics: +(sort grep /\w/, map { chr() } 0..255), "\n";
2600   $self->getfield($field) =~ /^([\w \,\.\-\']+)$/
2601     or return gettext('illegal_name'). " $field: ". $self->getfield($field);
2602   $self->setfield($field,$1);
2603   '';
2604 }
2605
2606 =item ut_zip COLUMN
2607
2608 Check/untaint zip codes.
2609
2610 =cut
2611
2612 my @zip_reqd_countries = qw( AU CA US ); #CA, US implicit...
2613
2614 sub ut_zip {
2615   my( $self, $field, $country ) = @_;
2616
2617   if ( $country eq 'US' ) {
2618
2619     $self->getfield($field) =~ /^\s*(\d{5}(\-\d{4})?)\s*$/
2620       or return gettext('illegal_zip'). " $field for country $country: ".
2621                 $self->getfield($field);
2622     $self->setfield($field, $1);
2623
2624   } elsif ( $country eq 'CA' ) {
2625
2626     $self->getfield($field) =~ /^\s*([A-Z]\d[A-Z])\s*(\d[A-Z]\d)\s*$/i
2627       or return gettext('illegal_zip'). " $field for country $country: ".
2628                 $self->getfield($field);
2629     $self->setfield($field, "$1 $2");
2630
2631   } else {
2632
2633     if ( $self->getfield($field) =~ /^\s*$/
2634          && ( !$country || ! grep { $_ eq $country } @zip_reqd_countries )
2635        )
2636     {
2637       $self->setfield($field,'');
2638     } else {
2639       $self->getfield($field) =~ /^\s*(\w[\w\-\s]{2,8}\w)\s*$/
2640         or return gettext('illegal_zip'). " $field: ". $self->getfield($field);
2641       $self->setfield($field,$1);
2642     }
2643
2644   }
2645
2646   '';
2647 }
2648
2649 =item ut_country COLUMN
2650
2651 Check/untaint country codes.  Country names are changed to codes, if possible -
2652 see L<Locale::Country>.
2653
2654 =cut
2655
2656 sub ut_country {
2657   my( $self, $field ) = @_;
2658   unless ( $self->getfield($field) =~ /^(\w\w)$/ ) {
2659     if ( $self->getfield($field) =~ /^([\w \,\.\(\)\']+)$/ 
2660          && country2code($1) ) {
2661       $self->setfield($field,uc(country2code($1)));
2662     }
2663   }
2664   $self->getfield($field) =~ /^(\w\w)$/
2665     or return "Illegal (country) $field: ". $self->getfield($field);
2666   $self->setfield($field,uc($1));
2667   '';
2668 }
2669
2670 =item ut_anything COLUMN
2671
2672 Untaints arbitrary data.  Be careful.
2673
2674 =cut
2675
2676 sub ut_anything {
2677   my( $self, $field ) = @_;
2678   $self->getfield($field) =~ /^(.*)$/s
2679     or return "Illegal $field: ". $self->getfield($field);
2680   $self->setfield($field,$1);
2681   '';
2682 }
2683
2684 =item ut_enum COLUMN CHOICES_ARRAYREF
2685
2686 Check/untaint a column, supplying all possible choices, like the "enum" type.
2687
2688 =cut
2689
2690 sub ut_enum {
2691   my( $self, $field, $choices ) = @_;
2692   foreach my $choice ( @$choices ) {
2693     if ( $self->getfield($field) eq $choice ) {
2694       $self->setfield($field, $choice);
2695       return '';
2696     }
2697   }
2698   return "Illegal (enum) field $field: ". $self->getfield($field);
2699 }
2700
2701 =item ut_enumn COLUMN CHOICES_ARRAYREF
2702
2703 Like ut_enum, except the null value is also allowed.
2704
2705 =cut
2706
2707 sub ut_enumn {
2708   my( $self, $field, $choices ) = @_;
2709   $self->getfield($field)
2710     ? $self->ut_enum($field, $choices)
2711     : '';
2712 }
2713
2714
2715 =item ut_foreign_key COLUMN FOREIGN_TABLE FOREIGN_COLUMN
2716
2717 Check/untaint a foreign column key.  Call a regular ut_ method (like ut_number)
2718 on the column first.
2719
2720 =cut
2721
2722 sub ut_foreign_key {
2723   my( $self, $field, $table, $foreign ) = @_;
2724   return '' if $no_check_foreign;
2725   qsearchs($table, { $foreign => $self->getfield($field) })
2726     or return "Can't find ". $self->table. ".$field ". $self->getfield($field).
2727               " in $table.$foreign";
2728   '';
2729 }
2730
2731 =item ut_foreign_keyn COLUMN FOREIGN_TABLE FOREIGN_COLUMN
2732
2733 Like ut_foreign_key, except the null value is also allowed.
2734
2735 =cut
2736
2737 sub ut_foreign_keyn {
2738   my( $self, $field, $table, $foreign ) = @_;
2739   $self->getfield($field)
2740     ? $self->ut_foreign_key($field, $table, $foreign)
2741     : '';
2742 }
2743
2744 =item ut_agentnum_acl COLUMN [ NULL_RIGHT | NULL_RIGHT_LISTREF ]
2745
2746 Checks this column as an agentnum, taking into account the current users's
2747 ACLs.  NULL_RIGHT or NULL_RIGHT_LISTREF, if specified, indicates the access
2748 right or rights allowing no agentnum.
2749
2750 =cut
2751
2752 sub ut_agentnum_acl {
2753   my( $self, $field ) = (shift, shift);
2754   my $null_acl = scalar(@_) ? shift : [];
2755   $null_acl = [ $null_acl ] unless ref($null_acl);
2756
2757   my $error = $self->ut_foreign_keyn($field, 'agent', 'agentnum');
2758   return "Illegal agentnum: $error" if $error;
2759
2760   my $curuser = $FS::CurrentUser::CurrentUser;
2761
2762   if ( $self->$field() ) {
2763
2764     return "Access denied"
2765       unless $curuser->agentnum($self->$field());
2766
2767   } else {
2768
2769     return "Access denied"
2770       unless grep $curuser->access_right($_), @$null_acl;
2771
2772   }
2773
2774   '';
2775
2776 }
2777
2778 =item virtual_fields [ TABLE ]
2779
2780 Returns a list of virtual fields defined for the table.  This should not 
2781 be exported, and should only be called as an instance or class method.
2782
2783 =cut
2784
2785 sub virtual_fields {
2786   my $self = shift;
2787   my $table;
2788   $table = $self->table or confess "virtual_fields called on non-table";
2789
2790   confess "Unknown table $table" unless dbdef->table($table);
2791
2792   return () unless dbdef->table('part_virtual_field');
2793
2794   unless ( $virtual_fields_cache{$table} ) {
2795     my $query = 'SELECT name from part_virtual_field ' .
2796                 "WHERE dbtable = '$table'";
2797     my $dbh = dbh;
2798     my $result = $dbh->selectcol_arrayref($query);
2799     confess "Error executing virtual fields query: $query: ". $dbh->errstr
2800       if $dbh->err;
2801     $virtual_fields_cache{$table} = $result;
2802   }
2803
2804   @{$virtual_fields_cache{$table}};
2805
2806 }
2807
2808
2809 =item fields [ TABLE ]
2810
2811 This is a wrapper for real_fields and virtual_fields.  Code that called
2812 fields before should probably continue to call fields.
2813
2814 =cut
2815
2816 sub fields {
2817   my $something = shift;
2818   my $table;
2819   if($something->isa('FS::Record')) {
2820     $table = $something->table;
2821   } else {
2822     $table = $something;
2823     $something = "FS::$table";
2824   }
2825   return (real_fields($table), $something->virtual_fields());
2826 }
2827
2828 =item pvf FIELD_NAME
2829
2830 Returns the FS::part_virtual_field object corresponding to a field in the 
2831 record (specified by FIELD_NAME).
2832
2833 =cut
2834
2835 sub pvf {
2836   my ($self, $name) = (shift, shift);
2837
2838   if(grep /^$name$/, $self->virtual_fields) {
2839     return qsearchs('part_virtual_field', { dbtable => $self->table,
2840                                             name    => $name } );
2841   }
2842   ''
2843 }
2844
2845 =item vfieldpart_hashref TABLE
2846
2847 Returns a hashref of virtual field names and vfieldparts applicable to the given
2848 TABLE.
2849
2850 =cut
2851
2852 sub vfieldpart_hashref {
2853   my $self = shift;
2854   my $table = $self->table;
2855
2856   return {} unless dbdef->table('part_virtual_field');
2857
2858   my $dbh = dbh;
2859   my $statement = "SELECT vfieldpart, name FROM part_virtual_field WHERE ".
2860                   "dbtable = '$table'";
2861   my $sth = $dbh->prepare($statement);
2862   $sth->execute or croak "Execution of '$statement' failed: ".$dbh->errstr;
2863   return { map { $_->{name}, $_->{vfieldpart} } 
2864     @{$sth->fetchall_arrayref({})} };
2865
2866 }
2867
2868 =item encrypt($value)
2869
2870 Encrypts the credit card using a combination of PK to encrypt and uuencode to armour.
2871
2872 Returns the encrypted string.
2873
2874 You should generally not have to worry about calling this, as the system handles this for you.
2875
2876 =cut
2877
2878 sub encrypt {
2879   my ($self, $value) = @_;
2880   my $encrypted;
2881
2882   if ($conf->exists('encryption')) {
2883     if ($self->is_encrypted($value)) {
2884       # Return the original value if it isn't plaintext.
2885       $encrypted = $value;
2886     } else {
2887       $self->loadRSA;
2888       if (ref($rsa_encrypt) =~ /::RSA/) { # We Can Encrypt
2889         # RSA doesn't like the empty string so let's pack it up
2890         # The database doesn't like the RSA data so uuencode it
2891         my $length = length($value)+1;
2892         $encrypted = pack("u*",$rsa_encrypt->encrypt(pack("Z$length",$value)));
2893       } else {
2894         die ("You can't encrypt w/o a valid RSA engine - Check your installation or disable encryption");
2895       }
2896     }
2897   }
2898   return $encrypted;
2899 }
2900
2901 =item is_encrypted($value)
2902
2903 Checks to see if the string is encrypted and returns true or false (1/0) to indicate it's status.
2904
2905 =cut
2906
2907
2908 sub is_encrypted {
2909   my ($self, $value) = @_;
2910   # Possible Bug - Some work may be required here....
2911
2912   if ($value =~ /^M/ && length($value) > 80) {
2913     return 1;
2914   } else {
2915     return 0;
2916   }
2917 }
2918
2919 =item decrypt($value)
2920
2921 Uses the private key to decrypt the string. Returns the decryoted string or undef on failure.
2922
2923 You should generally not have to worry about calling this, as the system handles this for you.
2924
2925 =cut
2926
2927 sub decrypt {
2928   my ($self,$value) = @_;
2929   my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
2930   if ($conf->exists('encryption') && $self->is_encrypted($value)) {
2931     $self->loadRSA;
2932     if (ref($rsa_decrypt) =~ /::RSA/) {
2933       my $encrypted = unpack ("u*", $value);
2934       $decrypted =  unpack("Z*", eval{$rsa_decrypt->decrypt($encrypted)});
2935       if ($@) {warn "Decryption Failed"};
2936     }
2937   }
2938   return $decrypted;
2939 }
2940
2941 sub loadRSA {
2942     my $self = shift;
2943     #Initialize the Module
2944     $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default
2945
2946     if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') {
2947       $rsa_module = $conf->config('encryptionmodule');
2948     }
2949
2950     if (!$rsa_loaded) {
2951         eval ("require $rsa_module"); # No need to import the namespace
2952         $rsa_loaded++;
2953     }
2954     # Initialize Encryption
2955     if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') {
2956       my $public_key = join("\n",$conf->config('encryptionpublickey'));
2957       $rsa_encrypt = $rsa_module->new_public_key($public_key);
2958     }
2959     
2960     # Intitalize Decryption
2961     if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') {
2962       my $private_key = join("\n",$conf->config('encryptionprivatekey'));
2963       $rsa_decrypt = $rsa_module->new_private_key($private_key);
2964     }
2965 }
2966
2967 =item h_search ACTION
2968
2969 Given an ACTION, either "insert", or "delete", returns the appropriate history
2970 record corresponding to this record, if any.
2971
2972 =cut
2973
2974 sub h_search {
2975   my( $self, $action ) = @_;
2976
2977   my $table = $self->table;
2978   $table =~ s/^h_//;
2979
2980   my $primary_key = dbdef->table($table)->primary_key;
2981
2982   qsearchs({
2983     'table'   => "h_$table",
2984     'hashref' => { $primary_key     => $self->$primary_key(),
2985                    'history_action' => $action,
2986                  },
2987   });
2988
2989 }
2990
2991 =item h_date ACTION
2992
2993 Given an ACTION, either "insert", or "delete", returns the timestamp of the
2994 appropriate history record corresponding to this record, if any.
2995
2996 =cut
2997
2998 sub h_date {
2999   my($self, $action) = @_;
3000   my $h = $self->h_search($action);
3001   $h ? $h->history_date : '';
3002 }
3003
3004 =item scalar_sql SQL [ PLACEHOLDER, ... ]
3005
3006 A class or object method.  Executes the sql statement represented by SQL and
3007 returns a scalar representing the result: the first column of the first row.
3008
3009 Dies on bogus SQL.  Returns an empty string if no row is returned.
3010
3011 Typically used for statments which return a single value such as "SELECT
3012 COUNT(*) FROM table WHERE something" OR "SELECT column FROM table WHERE key = ?"
3013
3014 =cut
3015
3016 sub scalar_sql {
3017   my($self, $sql) = (shift, shift);
3018   my $sth = dbh->prepare($sql) or die dbh->errstr;
3019   $sth->execute(@_)
3020     or die "Unexpected error executing statement $sql: ". $sth->errstr;
3021   my $row = $sth->fetchrow_arrayref or return '';
3022   my $scalar = $row->[0];
3023   defined($scalar) ? $scalar : '';
3024 }
3025
3026 =back
3027
3028 =head1 SUBROUTINES
3029
3030 =over 4
3031
3032 =item real_fields [ TABLE ]
3033
3034 Returns a list of the real columns in the specified table.  Called only by 
3035 fields() and other subroutines elsewhere in FS::Record.
3036
3037 =cut
3038
3039 sub real_fields {
3040   my $table = shift;
3041
3042   my($table_obj) = dbdef->table($table);
3043   confess "Unknown table $table" unless $table_obj;
3044   $table_obj->columns;
3045 }
3046
3047 =item _quote VALUE, TABLE, COLUMN
3048
3049 This is an internal function used to construct SQL statements.  It returns
3050 VALUE DBI-quoted (see L<DBI/"quote">) unless VALUE is a number and the column
3051 type (see L<DBIx::DBSchema::Column>) does not end in `char' or `binary'.
3052
3053 =cut
3054
3055 sub _quote {
3056   my($value, $table, $column) = @_;
3057   my $column_obj = dbdef->table($table)->column($column);
3058   my $column_type = $column_obj->type;
3059   my $nullable = $column_obj->null;
3060
3061   warn "  $table.$column: $value ($column_type".
3062        ( $nullable ? ' NULL' : ' NOT NULL' ).
3063        ")\n" if $DEBUG > 2;
3064
3065   if ( $value eq '' && $nullable ) {
3066     'NULL';
3067   } elsif ( $value eq '' && $column_type =~ /^(int|numeric)/ ) {
3068     cluck "WARNING: Attempting to set non-null integer $table.$column null; ".
3069           "using 0 instead";
3070     0;
3071   } elsif ( $value =~ /^\d+(\.\d+)?$/ && 
3072             ! $column_type =~ /(char|binary|text)$/i ) {
3073     $value;
3074   } elsif (( $column_type =~ /^bytea$/i || $column_type =~ /(blob|varbinary)/i )
3075            && driver_name eq 'Pg'
3076           )
3077   {
3078     no strict 'subs';
3079 #    dbh->quote($value, { pg_type => PG_BYTEA() }); # doesn't work right
3080     # Pg binary string quoting: convert each character to 3-digit octal prefixed with \\, 
3081     # single-quote the whole mess, and put an "E" in front.
3082     return ("E'" . join('', map { sprintf('\\\\%03o', ord($_)) } split(//, $value) ) . "'");
3083   } else {
3084     dbh->quote($value);
3085   }
3086 }
3087
3088 =item hfields TABLE
3089
3090 This is deprecated.  Don't use it.
3091
3092 It returns a hash-type list with the fields of this record's table set true.
3093
3094 =cut
3095
3096 sub hfields {
3097   carp "warning: hfields is deprecated";
3098   my($table)=@_;
3099   my(%hash);
3100   foreach (fields($table)) {
3101     $hash{$_}=1;
3102   }
3103   \%hash;
3104 }
3105
3106 sub _dump {
3107   my($self)=@_;
3108   join("\n", map {
3109     "$_: ". $self->getfield($_). "|"
3110   } (fields($self->table)) );
3111 }
3112
3113 sub DESTROY { return; }
3114
3115 #sub DESTROY {
3116 #  my $self = shift;
3117 #  #use Carp qw(cluck);
3118 #  #cluck "DESTROYING $self";
3119 #  warn "DESTROYING $self";
3120 #}
3121
3122 #sub is_tainted {
3123 #             return ! eval { join('',@_), kill 0; 1; };
3124 #         }
3125
3126 =item str2time_sql [ DRIVER_NAME ]
3127
3128 Returns a function to convert to unix time based on database type, such as
3129 "EXTRACT( EPOCH FROM" for Pg or "UNIX_TIMESTAMP(" for mysql.  See
3130 the str2time_sql_closing method to return a closing string rather than just
3131 using a closing parenthesis as previously suggested.
3132
3133 You can pass an optional driver name such as "Pg", "mysql" or
3134 $dbh->{Driver}->{Name} to return a function for that database instead of
3135 the current database.
3136
3137 =cut
3138
3139 sub str2time_sql { 
3140   my $driver = shift || driver_name;
3141
3142   return 'UNIX_TIMESTAMP('      if $driver =~ /^mysql/i;
3143   return 'EXTRACT( EPOCH FROM ' if $driver =~ /^Pg/i;
3144
3145   warn "warning: unknown database type $driver; guessing how to convert ".
3146        "dates to UNIX timestamps";
3147   return 'EXTRACT(EPOCH FROM ';
3148
3149 }
3150
3151 =item str2time_sql_closing [ DRIVER_NAME ]
3152
3153 Returns the closing suffix of a function to convert to unix time based on
3154 database type, such as ")::integer" for Pg or ")" for mysql.
3155
3156 You can pass an optional driver name such as "Pg", "mysql" or
3157 $dbh->{Driver}->{Name} to return a function for that database instead of
3158 the current database.
3159
3160 =cut
3161
3162 sub str2time_sql_closing { 
3163   my $driver = shift || driver_name;
3164
3165   return ' )::INTEGER ' if $driver =~ /^Pg/i;
3166   return ' ) ';
3167 }
3168
3169 =item regexp_sql [ DRIVER_NAME ]
3170
3171 Returns the operator to do a regular expression comparison based on database
3172 type, such as '~' for Pg or 'REGEXP' for mysql.
3173
3174 You can pass an optional driver name such as "Pg", "mysql" or
3175 $dbh->{Driver}->{Name} to return a function for that database instead of
3176 the current database.
3177
3178 =cut
3179
3180 sub regexp_sql {
3181   my $driver = shift || driver_name;
3182
3183   return '~'      if $driver =~ /^Pg/i;
3184   return 'REGEXP' if $driver =~ /^mysql/i;
3185
3186   die "don't know how to use regular expressions in ". driver_name." databases";
3187
3188 }
3189
3190 =item not_regexp_sql [ DRIVER_NAME ]
3191
3192 Returns the operator to do a regular expression negation based on database
3193 type, such as '!~' for Pg or 'NOT REGEXP' for mysql.
3194
3195 You can pass an optional driver name such as "Pg", "mysql" or
3196 $dbh->{Driver}->{Name} to return a function for that database instead of
3197 the current database.
3198
3199 =cut
3200
3201 sub not_regexp_sql {
3202   my $driver = shift || driver_name;
3203
3204   return '!~'         if $driver =~ /^Pg/i;
3205   return 'NOT REGEXP' if $driver =~ /^mysql/i;
3206
3207   die "don't know how to use regular expressions in ". driver_name." databases";
3208
3209 }
3210
3211 =item concat_sql [ DRIVER_NAME ] ITEMS_ARRAYREF
3212
3213 Returns the items concatendated based on database type, using "CONCAT()" for
3214 mysql and " || " for Pg and other databases.
3215
3216 You can pass an optional driver name such as "Pg", "mysql" or
3217 $dbh->{Driver}->{Name} to return a function for that database instead of
3218 the current database.
3219
3220 =cut
3221
3222 sub concat_sql {
3223   my $driver = ref($_[0]) ? driver_name : shift;
3224   my $items = shift;
3225
3226   if ( $driver =~ /^mysql/i ) {
3227     'CONCAT('. join(',', @$items). ')';
3228   } else {
3229     join('||', @$items);
3230   }
3231
3232 }
3233
3234 =back
3235
3236 =head1 BUGS
3237
3238 This module should probably be renamed, since much of the functionality is
3239 of general use.  It is not completely unlike Adapter::DBI (see below).
3240
3241 Exported qsearch and qsearchs should be deprecated in favor of method calls
3242 (against an FS::Record object like the old search and searchs that qsearch
3243 and qsearchs were on top of.)
3244
3245 The whole fields / hfields mess should be removed.
3246
3247 The various WHERE clauses should be subroutined.
3248
3249 table string should be deprecated in favor of DBIx::DBSchema::Table.
3250
3251 No doubt we could benefit from a Tied hash.  Documenting how exists / defined
3252 true maps to the database (and WHERE clauses) would also help.
3253
3254 The ut_ methods should ask the dbdef for a default length.
3255
3256 ut_sqltype (like ut_varchar) should all be defined
3257
3258 A fallback check method should be provided which uses the dbdef.
3259
3260 The ut_money method assumes money has two decimal digits.
3261
3262 The Pg money kludge in the new method only strips `$'.
3263
3264 The ut_phonen method only checks US-style phone numbers.
3265
3266 The _quote function should probably use ut_float instead of a regex.
3267
3268 All the subroutines probably should be methods, here or elsewhere.
3269
3270 Probably should borrow/use some dbdef methods where appropriate (like sub
3271 fields)
3272
3273 As of 1.14, DBI fetchall_hashref( {} ) doesn't set fetchrow_hashref NAME_lc,
3274 or allow it to be set.  Working around it is ugly any way around - DBI should
3275 be fixed.  (only affects RDBMS which return uppercase column names)
3276
3277 ut_zip should take an optional country like ut_phone.
3278
3279 =head1 SEE ALSO
3280
3281 L<DBIx::DBSchema>, L<FS::UID>, L<DBI>
3282
3283 Adapter::DBI from Ch. 11 of Advanced Perl Programming by Sriram Srinivasan.
3284
3285 http://poop.sf.net/
3286
3287 =cut
3288
3289 1;
3290