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