proper cdr_batch table, RT#6386
[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} ? @{ $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 %iopt = (
1565     #class-static
1566     table                      => $table,
1567     formats                    => \%formats,
1568     format_types               => $opt->{format_types},
1569     format_headers             => $opt->{format_headers},
1570     format_sep_chars           => $opt->{format_sep_chars},
1571     format_fixedlength_formats => $opt->{format_fixedlength_formats},
1572     #per-import
1573     job                        => $job,
1574     file                       => $file,
1575     #type                       => $type,
1576     format                     => $param->{format},
1577     params                     => { map { $_ => $param->{$_} } @pass_params },
1578     #?
1579     default_csv                => $opt->{default_csv},
1580   );
1581
1582   if ( $opt->{'batch_namecol'} ) {
1583     $iopt{'batch_namevalue'} = $param->{ $opt->{'batch_namecol'} };
1584     $iopt{$_} = $opt->{$_} foreach qw( batch_keycol batch_table batch_namecol );
1585   }
1586
1587   my $error = FS::Record::batch_import( \%iopt );
1588
1589   unlink $file;
1590
1591   die "$error\n" if $error;
1592 }
1593
1594 =item batch_import PARAM_HASHREF
1595
1596 Class method for batch imports.  Available params:
1597
1598 =over 4
1599
1600 =item table
1601
1602 =item formats
1603
1604 =item format_types
1605
1606 =item format_headers
1607
1608 =item format_sep_chars
1609
1610 =item format_fixedlength_formats
1611
1612 =item params
1613
1614 =item job
1615
1616 FS::queue object, will be updated with progress
1617
1618 =item file
1619
1620 =item type
1621
1622 csv, xls or fixedlength
1623
1624 =item format
1625
1626 =item empty_ok
1627
1628 =back
1629
1630 =cut
1631
1632 sub batch_import {
1633   my $param = shift;
1634
1635   warn "$me batch_import call with params: \n". Dumper($param)
1636     if $DEBUG;
1637
1638   my $table   = $param->{table};
1639   my $formats = $param->{formats};
1640
1641   my $job     = $param->{job};
1642   my $file    = $param->{file};
1643   my $format  = $param->{'format'};
1644   my $params  = $param->{params} || {};
1645
1646   die "unknown format $format" unless exists $formats->{ $format };
1647
1648   my $type = $param->{'format_types'}
1649              ? $param->{'format_types'}{ $format }
1650              : $param->{type} || 'csv';
1651
1652   unless ( $type ) {
1653     if ( $file =~ /\.(\w+)$/i ) {
1654       $type = lc($1);
1655     } else {
1656       #or error out???
1657       warn "can't parse file type from filename $file; defaulting to CSV";
1658       $type = 'csv';
1659     }
1660     $type = 'csv'
1661       if $param->{'default_csv'} && $type ne 'xls';
1662   }
1663
1664   my $header = $param->{'format_headers'}
1665                  ? $param->{'format_headers'}{ $param->{'format'} }
1666                  : 0;
1667
1668   my $sep_char = $param->{'format_sep_chars'}
1669                    ? $param->{'format_sep_chars'}{ $param->{'format'} }
1670                    : ',';
1671
1672   my $fixedlength_format =
1673     $param->{'format_fixedlength_formats'}
1674       ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
1675       : '';
1676
1677   my @fields = @{ $formats->{ $format } };
1678
1679   my $row = 0;
1680   my $count;
1681   my $parser;
1682   my @buffer = ();
1683   if ( $type eq 'csv' || $type eq 'fixedlength' ) {
1684
1685     if ( $type eq 'csv' ) {
1686
1687       my %attr = ();
1688       $attr{sep_char} = $sep_char if $sep_char;
1689       $parser = new Text::CSV_XS \%attr;
1690
1691     } elsif ( $type eq 'fixedlength' ) {
1692
1693       eval "use Parse::FixedLength;";
1694       die $@ if $@;
1695       $parser = new Parse::FixedLength $fixedlength_format;
1696  
1697     } else {
1698       die "Unknown file type $type\n";
1699     }
1700
1701     @buffer = split(/\r?\n/, slurp($file) );
1702     splice(@buffer, 0, ($header || 0) );
1703     $count = scalar(@buffer);
1704
1705   } elsif ( $type eq 'xls' ) {
1706
1707     eval "use Spreadsheet::ParseExcel;";
1708     die $@ if $@;
1709
1710     eval "use DateTime::Format::Excel;";
1711     #for now, just let the error be thrown if it is used, since only CDR
1712     # formats bill_west and troop use it, not other excel-parsing things
1713     #die $@ if $@;
1714
1715     my $excel = Spreadsheet::ParseExcel::Workbook->new->Parse($file);
1716
1717     $parser = $excel->{Worksheet}[0]; #first sheet
1718
1719     $count = $parser->{MaxRow} || $parser->{MinRow};
1720     $count++;
1721
1722     $row = $header || 0;
1723
1724   } else {
1725     die "Unknown file type $type\n";
1726   }
1727
1728   #my $columns;
1729
1730   local $SIG{HUP} = 'IGNORE';
1731   local $SIG{INT} = 'IGNORE';
1732   local $SIG{QUIT} = 'IGNORE';
1733   local $SIG{TERM} = 'IGNORE';
1734   local $SIG{TSTP} = 'IGNORE';
1735   local $SIG{PIPE} = 'IGNORE';
1736
1737   my $oldAutoCommit = $FS::UID::AutoCommit;
1738   local $FS::UID::AutoCommit = 0;
1739   my $dbh = dbh;
1740
1741   if ( $param->{'batch_namecol'} && $param->{'batch_namevalue'} ) {
1742     my $batch_col   = $param->{'batch_keycol'};
1743
1744     my $batch_class = 'FS::'. $param->{'batch_table'};
1745     my $batch = $batch_class->new({
1746       $param->{'batch_namecol'} => $param->{'batch_namevalue'}
1747     });
1748     my $error = $batch->insert;
1749     if ( $error ) {
1750       $dbh->rollback if $oldAutoCommit;
1751       return "can't insert batch record: $error";
1752     }
1753     #primary key via dbdef? (so the column names don't have to match)
1754     my $batch_value = $batch->get( $param->{'batch_keycol'} );
1755
1756     $params->{ $batch_col } = $batch_value;
1757   }
1758   
1759   my $line;
1760   my $imported = 0;
1761   my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
1762   while (1) {
1763
1764     my @columns = ();
1765     if ( $type eq 'csv' ) {
1766
1767       last unless scalar(@buffer);
1768       $line = shift(@buffer);
1769
1770       $parser->parse($line) or do {
1771         $dbh->rollback if $oldAutoCommit;
1772         return "can't parse: ". $parser->error_input();
1773       };
1774       @columns = $parser->fields();
1775
1776     } elsif ( $type eq 'fixedlength' ) {
1777
1778       @columns = $parser->parse($line);
1779
1780     } elsif ( $type eq 'xls' ) {
1781
1782       last if $row > ($parser->{MaxRow} || $parser->{MinRow})
1783            || ! $parser->{Cells}[$row];
1784
1785       my @row = @{ $parser->{Cells}[$row] };
1786       @columns = map $_->{Val}, @row;
1787
1788       #my $z = 'A';
1789       #warn $z++. ": $_\n" for @columns;
1790
1791     } else {
1792       die "Unknown file type $type\n";
1793     }
1794
1795     my @later = ();
1796     my %hash = %$params;
1797
1798     foreach my $field ( @fields ) {
1799
1800       my $value = shift @columns;
1801      
1802       if ( ref($field) eq 'CODE' ) {
1803         #&{$field}(\%hash, $value);
1804         push @later, $field, $value;
1805       } else {
1806         #??? $hash{$field} = $value if length($value);
1807         $hash{$field} = $value if defined($value) && length($value);
1808       }
1809
1810     }
1811
1812     my $class = "FS::$table";
1813
1814     my $record = $class->new( \%hash );
1815
1816     my $param = {};
1817     while ( scalar(@later) ) {
1818       my $sub = shift @later;
1819       my $data = shift @later;
1820       &{$sub}($record, $data, $conf, $param); # $record->&{$sub}($data, $conf);
1821       last if exists( $param->{skiprow} );
1822     }
1823     next if exists( $param->{skiprow} );
1824
1825     my $error = $record->insert;
1826
1827     if ( $error ) {
1828       $dbh->rollback if $oldAutoCommit;
1829       return "can't insert record". ( $line ? " for $line" : '' ). ": $error";
1830     }
1831
1832     $row++;
1833     $imported++;
1834
1835     if ( $job && time - $min_sec > $last ) { #progress bar
1836       $job->update_statustext( int(100 * $imported / $count) );
1837       $last = time;
1838     }
1839
1840   }
1841
1842   $dbh->commit or die $dbh->errstr if $oldAutoCommit;;
1843
1844   return "Empty file!" unless $imported || $param->{empty_ok};
1845
1846   ''; #no error
1847
1848 }
1849
1850 sub _h_statement {
1851   my( $self, $action, $time ) = @_;
1852
1853   $time ||= time;
1854
1855   my %nohistory = map { $_=>1 } $self->nohistory_fields;
1856
1857   my @fields =
1858     grep { defined($self->get($_)) && $self->get($_) ne "" && ! $nohistory{$_} }
1859     real_fields($self->table);
1860   ;
1861
1862   # If we're encrypting then don't store the payinfo in the history
1863   if ( $conf && $conf->exists('encryption') ) {
1864     @fields = grep { $_ ne 'payinfo' } @fields;
1865   }
1866
1867   my @values = map { _quote( $self->getfield($_), $self->table, $_) } @fields;
1868
1869   "INSERT INTO h_". $self->table. " ( ".
1870       join(', ', qw(history_date history_user history_action), @fields ).
1871     ") VALUES (".
1872       join(', ', $time, dbh->quote(getotaker()), dbh->quote($action), @values).
1873     ")"
1874   ;
1875 }
1876
1877 =item unique COLUMN
1878
1879 B<Warning>: External use is B<deprecated>.  
1880
1881 Replaces COLUMN in record with a unique number, using counters in the
1882 filesystem.  Used by the B<insert> method on single-field unique columns
1883 (see L<DBIx::DBSchema::Table>) and also as a fallback for primary keys
1884 that aren't SERIAL (Pg) or AUTO_INCREMENT (mysql).
1885
1886 Returns the new value.
1887
1888 =cut
1889
1890 sub unique {
1891   my($self,$field) = @_;
1892   my($table)=$self->table;
1893
1894   croak "Unique called on field $field, but it is ",
1895         $self->getfield($field),
1896         ", not null!"
1897     if $self->getfield($field);
1898
1899   #warn "table $table is tainted" if is_tainted($table);
1900   #warn "field $field is tainted" if is_tainted($field);
1901
1902   my($counter) = new File::CounterFile "$table.$field",0;
1903 # hack for web demo
1904 #  getotaker() =~ /^([\w\-]{1,16})$/ or die "Illegal CGI REMOTE_USER!";
1905 #  my($user)=$1;
1906 #  my($counter) = new File::CounterFile "$user/$table.$field",0;
1907 # endhack
1908
1909   my $index = $counter->inc;
1910   $index = $counter->inc while qsearchs($table, { $field=>$index } );
1911
1912   $index =~ /^(\d*)$/;
1913   $index=$1;
1914
1915   $self->setfield($field,$index);
1916
1917 }
1918
1919 =item ut_float COLUMN
1920
1921 Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May not be
1922 null.  If there is an error, returns the error, otherwise returns false.
1923
1924 =cut
1925
1926 sub ut_float {
1927   my($self,$field)=@_ ;
1928   ($self->getfield($field) =~ /^\s*(\d+\.\d+)\s*$/ ||
1929    $self->getfield($field) =~ /^\s*(\d+)\s*$/ ||
1930    $self->getfield($field) =~ /^\s*(\d+\.\d+e\d+)\s*$/ ||
1931    $self->getfield($field) =~ /^\s*(\d+e\d+)\s*$/)
1932     or return "Illegal or empty (float) $field: ". $self->getfield($field);
1933   $self->setfield($field,$1);
1934   '';
1935 }
1936 =item ut_floatn COLUMN
1937
1938 Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May be
1939 null.  If there is an error, returns the error, otherwise returns false.
1940
1941 =cut
1942
1943 #false laziness w/ut_ipn
1944 sub ut_floatn {
1945   my( $self, $field ) = @_;
1946   if ( $self->getfield($field) =~ /^()$/ ) {
1947     $self->setfield($field,'');
1948     '';
1949   } else {
1950     $self->ut_float($field);
1951   }
1952 }
1953
1954 =item ut_sfloat COLUMN
1955
1956 Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10.
1957 May not be null.  If there is an error, returns the error, otherwise returns
1958 false.
1959
1960 =cut
1961
1962 sub ut_sfloat {
1963   my($self,$field)=@_ ;
1964   ($self->getfield($field) =~ /^\s*(-?\d+\.\d+)\s*$/ ||
1965    $self->getfield($field) =~ /^\s*(-?\d+)\s*$/ ||
1966    $self->getfield($field) =~ /^\s*(-?\d+\.\d+[eE]-?\d+)\s*$/ ||
1967    $self->getfield($field) =~ /^\s*(-?\d+[eE]-?\d+)\s*$/)
1968     or return "Illegal or empty (float) $field: ". $self->getfield($field);
1969   $self->setfield($field,$1);
1970   '';
1971 }
1972 =item ut_sfloatn COLUMN
1973
1974 Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May be
1975 null.  If there is an error, returns the error, otherwise returns false.
1976
1977 =cut
1978
1979 sub ut_sfloatn {
1980   my( $self, $field ) = @_;
1981   if ( $self->getfield($field) =~ /^()$/ ) {
1982     $self->setfield($field,'');
1983     '';
1984   } else {
1985     $self->ut_sfloat($field);
1986   }
1987 }
1988
1989 =item ut_snumber COLUMN
1990
1991 Check/untaint signed numeric data (whole numbers).  If there is an error,
1992 returns the error, otherwise returns false.
1993
1994 =cut
1995
1996 sub ut_snumber {
1997   my($self, $field) = @_;
1998   $self->getfield($field) =~ /^\s*(-?)\s*(\d+)\s*$/
1999     or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
2000   $self->setfield($field, "$1$2");
2001   '';
2002 }
2003
2004 =item ut_snumbern COLUMN
2005
2006 Check/untaint signed numeric data (whole numbers).  If there is an error,
2007 returns the error, otherwise returns false.
2008
2009 =cut
2010
2011 sub ut_snumbern {
2012   my($self, $field) = @_;
2013   $self->getfield($field) =~ /^\s*(-?)\s*(\d*)\s*$/
2014     or return "Illegal (numeric) $field: ". $self->getfield($field);
2015   if ($1) {
2016     return "Illegal (numeric) $field: ". $self->getfield($field)
2017       unless $2;
2018   }
2019   $self->setfield($field, "$1$2");
2020   '';
2021 }
2022
2023 =item ut_number COLUMN
2024
2025 Check/untaint simple numeric data (whole numbers).  May not be null.  If there
2026 is an error, returns the error, otherwise returns false.
2027
2028 =cut
2029
2030 sub ut_number {
2031   my($self,$field)=@_;
2032   $self->getfield($field) =~ /^\s*(\d+)\s*$/
2033     or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
2034   $self->setfield($field,$1);
2035   '';
2036 }
2037
2038 =item ut_numbern COLUMN
2039
2040 Check/untaint simple numeric data (whole numbers).  May be null.  If there is
2041 an error, returns the error, otherwise returns false.
2042
2043 =cut
2044
2045 sub ut_numbern {
2046   my($self,$field)=@_;
2047   $self->getfield($field) =~ /^\s*(\d*)\s*$/
2048     or return "Illegal (numeric) $field: ". $self->getfield($field);
2049   $self->setfield($field,$1);
2050   '';
2051 }
2052
2053 =item ut_money COLUMN
2054
2055 Check/untaint monetary numbers.  May be negative.  Set to 0 if null.  If there
2056 is an error, returns the error, otherwise returns false.
2057
2058 =cut
2059
2060 sub ut_money {
2061   my($self,$field)=@_;
2062   $self->setfield($field, 0) if $self->getfield($field) eq '';
2063   $self->getfield($field) =~ /^\s*(\-)?\s*(\d*)(\.\d{2})?\s*$/
2064     or return "Illegal (money) $field: ". $self->getfield($field);
2065   #$self->setfield($field, "$1$2$3" || 0);
2066   $self->setfield($field, ( ($1||''). ($2||''). ($3||'') ) || 0);
2067   '';
2068 }
2069
2070 =item ut_moneyn COLUMN
2071
2072 Check/untaint monetary numbers.  May be negative.  If there
2073 is an error, returns the error, otherwise returns false.
2074
2075 =cut
2076
2077 sub ut_moneyn {
2078   my($self,$field)=@_;
2079   if ($self->getfield($field) eq '') {
2080     $self->setfield($field, '');
2081     return '';
2082   }
2083   $self->ut_money($field);
2084 }
2085
2086 =item ut_text COLUMN
2087
2088 Check/untaint text.  Alphanumerics, spaces, and the following punctuation
2089 symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < >
2090 May not be null.  If there is an error, returns the error, otherwise returns
2091 false.
2092
2093 =cut
2094
2095 sub ut_text {
2096   my($self,$field)=@_;
2097   #warn "msgcat ". \&msgcat. "\n";
2098   #warn "notexist ". \&notexist. "\n";
2099   #warn "AUTOLOAD ". \&AUTOLOAD. "\n";
2100   $self->getfield($field)
2101     =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>]+)$/
2102       or return gettext('illegal_or_empty_text'). " $field: ".
2103                  $self->getfield($field);
2104   $self->setfield($field,$1);
2105   '';
2106 }
2107
2108 =item ut_textn COLUMN
2109
2110 Check/untaint text.  Alphanumerics, spaces, and the following punctuation
2111 symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? /
2112 May be null.  If there is an error, returns the error, otherwise returns false.
2113
2114 =cut
2115
2116 sub ut_textn {
2117   my($self,$field)=@_;
2118   $self->getfield($field)
2119     =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
2120       or return gettext('illegal_text'). " $field: ". $self->getfield($field);
2121   $self->setfield($field,$1);
2122   '';
2123 }
2124
2125 =item ut_alpha COLUMN
2126
2127 Check/untaint alphanumeric strings (no spaces).  May not be null.  If there is
2128 an error, returns the error, otherwise returns false.
2129
2130 =cut
2131
2132 sub ut_alpha {
2133   my($self,$field)=@_;
2134   $self->getfield($field) =~ /^(\w+)$/
2135     or return "Illegal or empty (alphanumeric) $field: ".
2136               $self->getfield($field);
2137   $self->setfield($field,$1);
2138   '';
2139 }
2140
2141 =item ut_alpha COLUMN
2142
2143 Check/untaint alphanumeric strings (no spaces).  May be null.  If there is an
2144 error, returns the error, otherwise returns false.
2145
2146 =cut
2147
2148 sub ut_alphan {
2149   my($self,$field)=@_;
2150   $self->getfield($field) =~ /^(\w*)$/ 
2151     or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
2152   $self->setfield($field,$1);
2153   '';
2154 }
2155
2156 =item ut_alpha_lower COLUMN
2157
2158 Check/untaint lowercase alphanumeric strings (no spaces).  May not be null.  If
2159 there is an error, returns the error, otherwise returns false.
2160
2161 =cut
2162
2163 sub ut_alpha_lower {
2164   my($self,$field)=@_;
2165   $self->getfield($field) =~ /[[:upper:]]/
2166     and return "Uppercase characters are not permitted in $field";
2167   $self->ut_alpha($field);
2168 }
2169
2170 =item ut_phonen COLUMN [ COUNTRY ]
2171
2172 Check/untaint phone numbers.  May be null.  If there is an error, returns
2173 the error, otherwise returns false.
2174
2175 Takes an optional two-letter ISO country code; without it or with unsupported
2176 countries, ut_phonen simply calls ut_alphan.
2177
2178 =cut
2179
2180 sub ut_phonen {
2181   my( $self, $field, $country ) = @_;
2182   return $self->ut_alphan($field) unless defined $country;
2183   my $phonen = $self->getfield($field);
2184   if ( $phonen eq '' ) {
2185     $self->setfield($field,'');
2186   } elsif ( $country eq 'US' || $country eq 'CA' ) {
2187     $phonen =~ s/\D//g;
2188     $phonen = $conf->config('cust_main-default_areacode').$phonen
2189       if length($phonen)==7 && $conf->config('cust_main-default_areacode');
2190     $phonen =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/
2191       or return gettext('illegal_phone'). " $field: ". $self->getfield($field);
2192     $phonen = "$1-$2-$3";
2193     $phonen .= " x$4" if $4;
2194     $self->setfield($field,$phonen);
2195   } else {
2196     warn "warning: don't know how to check phone numbers for country $country";
2197     return $self->ut_textn($field);
2198   }
2199   '';
2200 }
2201
2202 =item ut_hex COLUMN
2203
2204 Check/untaint hexadecimal values.
2205
2206 =cut
2207
2208 sub ut_hex {
2209   my($self, $field) = @_;
2210   $self->getfield($field) =~ /^([\da-fA-F]+)$/
2211     or return "Illegal (hex) $field: ". $self->getfield($field);
2212   $self->setfield($field, uc($1));
2213   '';
2214 }
2215
2216 =item ut_hexn COLUMN
2217
2218 Check/untaint hexadecimal values.  May be null.
2219
2220 =cut
2221
2222 sub ut_hexn {
2223   my($self, $field) = @_;
2224   $self->getfield($field) =~ /^([\da-fA-F]*)$/
2225     or return "Illegal (hex) $field: ". $self->getfield($field);
2226   $self->setfield($field, uc($1));
2227   '';
2228 }
2229 =item ut_ip COLUMN
2230
2231 Check/untaint ip addresses.  IPv4 only for now, though ::1 is auto-translated
2232 to 127.0.0.1.
2233
2234 =cut
2235
2236 sub ut_ip {
2237   my( $self, $field ) = @_;
2238   $self->setfield($field, '127.0.0.1') if $self->getfield($field) eq '::1';
2239   $self->getfield($field) =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
2240     or return "Illegal (IP address) $field: ". $self->getfield($field);
2241   for ( $1, $2, $3, $4 ) { return "Illegal (IP address) $field" if $_ > 255; }
2242   $self->setfield($field, "$1.$2.$3.$4");
2243   '';
2244 }
2245
2246 =item ut_ipn COLUMN
2247
2248 Check/untaint ip addresses.  IPv4 only for now, though ::1 is auto-translated
2249 to 127.0.0.1.  May be null.
2250
2251 =cut
2252
2253 sub ut_ipn {
2254   my( $self, $field ) = @_;
2255   if ( $self->getfield($field) =~ /^()$/ ) {
2256     $self->setfield($field,'');
2257     '';
2258   } else {
2259     $self->ut_ip($field);
2260   }
2261 }
2262
2263 =item ut_coord COLUMN [ LOWER [ UPPER ] ]
2264
2265 Check/untaint coordinates.
2266 Accepts the following forms:
2267 DDD.DDDDD
2268 -DDD.DDDDD
2269 DDD MM.MMM
2270 -DDD MM.MMM
2271 DDD MM SS
2272 -DDD MM SS
2273 DDD MM MMM
2274 -DDD MM MMM
2275
2276 The "DDD MM SS" and "DDD MM MMM" are potentially ambiguous.
2277 The latter form (that is, the MMM are thousands of minutes) is
2278 assumed if the "MMM" is exactly three digits or two digits > 59.
2279
2280 To be safe, just use the DDD.DDDDD form.
2281
2282 If LOWER or UPPER are specified, then the coordinate is checked
2283 for lower and upper bounds, respectively.
2284
2285 =cut
2286
2287 sub ut_coord {
2288
2289   my ($self, $field) = (shift, shift);
2290
2291   my $lower = shift if scalar(@_);
2292   my $upper = shift if scalar(@_);
2293   my $coord = $self->getfield($field);
2294   my $neg = $coord =~ s/^(-)//;
2295
2296   my ($d, $m, $s) = (0, 0, 0);
2297
2298   if (
2299     (($d) = ($coord =~ /^(\s*\d{1,3}(?:\.\d+)?)\s*$/)) ||
2300     (($d, $m) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2}(?:\.\d+))\s*$/)) ||
2301     (($d, $m, $s) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2})\s+(\d{1,3})\s*$/))
2302   ) {
2303     $s = (((($s =~ /^\d{3}$/) or $s > 59) ? ($s / 1000) : ($s / 60)) / 60);
2304     $m = $m / 60;
2305     if ($m > 59) {
2306       return "Invalid (coordinate with minutes > 59) $field: "
2307              . $self->getfield($field);
2308     }
2309
2310     $coord = ($neg ? -1 : 1) * sprintf('%.8f', $d + $m + $s);
2311
2312     if (defined($lower) and ($coord < $lower)) {
2313       return "Invalid (coordinate < $lower) $field: "
2314              . $self->getfield($field);;
2315     }
2316
2317     if (defined($upper) and ($coord > $upper)) {
2318       return "Invalid (coordinate > $upper) $field: "
2319              . $self->getfield($field);;
2320     }
2321
2322     $self->setfield($field, $coord);
2323     return '';
2324   }
2325
2326   return "Invalid (coordinate) $field: " . $self->getfield($field);
2327
2328 }
2329
2330 =item ut_coordn COLUMN [ LOWER [ UPPER ] ]
2331
2332 Same as ut_coord, except optionally null.
2333
2334 =cut
2335
2336 sub ut_coordn {
2337
2338   my ($self, $field) = (shift, shift);
2339
2340   if ($self->getfield($field) =~ /^$/) {
2341     return '';
2342   } else {
2343     return $self->ut_coord($field, @_);
2344   }
2345
2346 }
2347
2348
2349 =item ut_domain COLUMN
2350
2351 Check/untaint host and domain names.
2352
2353 =cut
2354
2355 sub ut_domain {
2356   my( $self, $field ) = @_;
2357   #$self->getfield($field) =~/^(\w+\.)*\w+$/
2358   $self->getfield($field) =~/^(([\w\-]+\.)*\w+)$/
2359     or return "Illegal (domain) $field: ". $self->getfield($field);
2360   $self->setfield($field,$1);
2361   '';
2362 }
2363
2364 =item ut_name COLUMN
2365
2366 Check/untaint proper names; allows alphanumerics, spaces and the following
2367 punctuation: , . - '
2368
2369 May not be null.
2370
2371 =cut
2372
2373 sub ut_name {
2374   my( $self, $field ) = @_;
2375   $self->getfield($field) =~ /^([\w \,\.\-\']+)$/
2376     or return gettext('illegal_name'). " $field: ". $self->getfield($field);
2377   $self->setfield($field,$1);
2378   '';
2379 }
2380
2381 =item ut_zip COLUMN
2382
2383 Check/untaint zip codes.
2384
2385 =cut
2386
2387 my @zip_reqd_countries = qw( AU CA US ); #CA, US implicit...
2388
2389 sub ut_zip {
2390   my( $self, $field, $country ) = @_;
2391
2392   if ( $country eq 'US' ) {
2393
2394     $self->getfield($field) =~ /^\s*(\d{5}(\-\d{4})?)\s*$/
2395       or return gettext('illegal_zip'). " $field for country $country: ".
2396                 $self->getfield($field);
2397     $self->setfield($field, $1);
2398
2399   } elsif ( $country eq 'CA' ) {
2400
2401     $self->getfield($field) =~ /^\s*([A-Z]\d[A-Z])\s*(\d[A-Z]\d)\s*$/i
2402       or return gettext('illegal_zip'). " $field for country $country: ".
2403                 $self->getfield($field);
2404     $self->setfield($field, "$1 $2");
2405
2406   } else {
2407
2408     if ( $self->getfield($field) =~ /^\s*$/
2409          && ( !$country || ! grep { $_ eq $country } @zip_reqd_countries )
2410        )
2411     {
2412       $self->setfield($field,'');
2413     } else {
2414       $self->getfield($field) =~ /^\s*(\w[\w\-\s]{2,8}\w)\s*$/
2415         or return gettext('illegal_zip'). " $field: ". $self->getfield($field);
2416       $self->setfield($field,$1);
2417     }
2418
2419   }
2420
2421   '';
2422 }
2423
2424 =item ut_country COLUMN
2425
2426 Check/untaint country codes.  Country names are changed to codes, if possible -
2427 see L<Locale::Country>.
2428
2429 =cut
2430
2431 sub ut_country {
2432   my( $self, $field ) = @_;
2433   unless ( $self->getfield($field) =~ /^(\w\w)$/ ) {
2434     if ( $self->getfield($field) =~ /^([\w \,\.\(\)\']+)$/ 
2435          && country2code($1) ) {
2436       $self->setfield($field,uc(country2code($1)));
2437     }
2438   }
2439   $self->getfield($field) =~ /^(\w\w)$/
2440     or return "Illegal (country) $field: ". $self->getfield($field);
2441   $self->setfield($field,uc($1));
2442   '';
2443 }
2444
2445 =item ut_anything COLUMN
2446
2447 Untaints arbitrary data.  Be careful.
2448
2449 =cut
2450
2451 sub ut_anything {
2452   my( $self, $field ) = @_;
2453   $self->getfield($field) =~ /^(.*)$/s
2454     or return "Illegal $field: ". $self->getfield($field);
2455   $self->setfield($field,$1);
2456   '';
2457 }
2458
2459 =item ut_enum COLUMN CHOICES_ARRAYREF
2460
2461 Check/untaint a column, supplying all possible choices, like the "enum" type.
2462
2463 =cut
2464
2465 sub ut_enum {
2466   my( $self, $field, $choices ) = @_;
2467   foreach my $choice ( @$choices ) {
2468     if ( $self->getfield($field) eq $choice ) {
2469       $self->setfield($field, $choice);
2470       return '';
2471     }
2472   }
2473   return "Illegal (enum) field $field: ". $self->getfield($field);
2474 }
2475
2476 =item ut_foreign_key COLUMN FOREIGN_TABLE FOREIGN_COLUMN
2477
2478 Check/untaint a foreign column key.  Call a regular ut_ method (like ut_number)
2479 on the column first.
2480
2481 =cut
2482
2483 sub ut_foreign_key {
2484   my( $self, $field, $table, $foreign ) = @_;
2485   return '' if $no_check_foreign;
2486   qsearchs($table, { $foreign => $self->getfield($field) })
2487     or return "Can't find ". $self->table. ".$field ". $self->getfield($field).
2488               " in $table.$foreign";
2489   '';
2490 }
2491
2492 =item ut_foreign_keyn COLUMN FOREIGN_TABLE FOREIGN_COLUMN
2493
2494 Like ut_foreign_key, except the null value is also allowed.
2495
2496 =cut
2497
2498 sub ut_foreign_keyn {
2499   my( $self, $field, $table, $foreign ) = @_;
2500   $self->getfield($field)
2501     ? $self->ut_foreign_key($field, $table, $foreign)
2502     : '';
2503 }
2504
2505 =item ut_agentnum_acl COLUMN [ NULL_RIGHT | NULL_RIGHT_LISTREF ]
2506
2507 Checks this column as an agentnum, taking into account the current users's
2508 ACLs.  NULL_RIGHT or NULL_RIGHT_LISTREF, if specified, indicates the access
2509 right or rights allowing no agentnum.
2510
2511 =cut
2512
2513 sub ut_agentnum_acl {
2514   my( $self, $field ) = (shift, shift);
2515   my $null_acl = scalar(@_) ? shift : [];
2516   $null_acl = [ $null_acl ] unless ref($null_acl);
2517
2518   my $error = $self->ut_foreign_keyn($field, 'agent', 'agentnum');
2519   return "Illegal agentnum: $error" if $error;
2520
2521   my $curuser = $FS::CurrentUser::CurrentUser;
2522
2523   if ( $self->$field() ) {
2524
2525     return "Access denied"
2526       unless $curuser->agentnum($self->$field());
2527
2528   } else {
2529
2530     return "Access denied"
2531       unless grep $curuser->access_right($_), @$null_acl;
2532
2533   }
2534
2535   '';
2536
2537 }
2538
2539 =item virtual_fields [ TABLE ]
2540
2541 Returns a list of virtual fields defined for the table.  This should not 
2542 be exported, and should only be called as an instance or class method.
2543
2544 =cut
2545
2546 sub virtual_fields {
2547   my $self = shift;
2548   my $table;
2549   $table = $self->table or confess "virtual_fields called on non-table";
2550
2551   confess "Unknown table $table" unless dbdef->table($table);
2552
2553   return () unless dbdef->table('part_virtual_field');
2554
2555   unless ( $virtual_fields_cache{$table} ) {
2556     my $query = 'SELECT name from part_virtual_field ' .
2557                 "WHERE dbtable = '$table'";
2558     my $dbh = dbh;
2559     my $result = $dbh->selectcol_arrayref($query);
2560     confess "Error executing virtual fields query: $query: ". $dbh->errstr
2561       if $dbh->err;
2562     $virtual_fields_cache{$table} = $result;
2563   }
2564
2565   @{$virtual_fields_cache{$table}};
2566
2567 }
2568
2569
2570 =item fields [ TABLE ]
2571
2572 This is a wrapper for real_fields and virtual_fields.  Code that called
2573 fields before should probably continue to call fields.
2574
2575 =cut
2576
2577 sub fields {
2578   my $something = shift;
2579   my $table;
2580   if($something->isa('FS::Record')) {
2581     $table = $something->table;
2582   } else {
2583     $table = $something;
2584     $something = "FS::$table";
2585   }
2586   return (real_fields($table), $something->virtual_fields());
2587 }
2588
2589 =item pvf FIELD_NAME
2590
2591 Returns the FS::part_virtual_field object corresponding to a field in the 
2592 record (specified by FIELD_NAME).
2593
2594 =cut
2595
2596 sub pvf {
2597   my ($self, $name) = (shift, shift);
2598
2599   if(grep /^$name$/, $self->virtual_fields) {
2600     return qsearchs('part_virtual_field', { dbtable => $self->table,
2601                                             name    => $name } );
2602   }
2603   ''
2604 }
2605
2606 =item vfieldpart_hashref TABLE
2607
2608 Returns a hashref of virtual field names and vfieldparts applicable to the given
2609 TABLE.
2610
2611 =cut
2612
2613 sub vfieldpart_hashref {
2614   my $self = shift;
2615   my $table = $self->table;
2616
2617   return {} unless dbdef->table('part_virtual_field');
2618
2619   my $dbh = dbh;
2620   my $statement = "SELECT vfieldpart, name FROM part_virtual_field WHERE ".
2621                   "dbtable = '$table'";
2622   my $sth = $dbh->prepare($statement);
2623   $sth->execute or croak "Execution of '$statement' failed: ".$dbh->errstr;
2624   return { map { $_->{name}, $_->{vfieldpart} } 
2625     @{$sth->fetchall_arrayref({})} };
2626
2627 }
2628
2629 =item encrypt($value)
2630
2631 Encrypts the credit card using a combination of PK to encrypt and uuencode to armour.
2632
2633 Returns the encrypted string.
2634
2635 You should generally not have to worry about calling this, as the system handles this for you.
2636
2637 =cut
2638
2639 sub encrypt {
2640   my ($self, $value) = @_;
2641   my $encrypted;
2642
2643   if ($conf->exists('encryption')) {
2644     if ($self->is_encrypted($value)) {
2645       # Return the original value if it isn't plaintext.
2646       $encrypted = $value;
2647     } else {
2648       $self->loadRSA;
2649       if (ref($rsa_encrypt) =~ /::RSA/) { # We Can Encrypt
2650         # RSA doesn't like the empty string so let's pack it up
2651         # The database doesn't like the RSA data so uuencode it
2652         my $length = length($value)+1;
2653         $encrypted = pack("u*",$rsa_encrypt->encrypt(pack("Z$length",$value)));
2654       } else {
2655         die ("You can't encrypt w/o a valid RSA engine - Check your installation or disable encryption");
2656       }
2657     }
2658   }
2659   return $encrypted;
2660 }
2661
2662 =item is_encrypted($value)
2663
2664 Checks to see if the string is encrypted and returns true or false (1/0) to indicate it's status.
2665
2666 =cut
2667
2668
2669 sub is_encrypted {
2670   my ($self, $value) = @_;
2671   # Possible Bug - Some work may be required here....
2672
2673   if ($value =~ /^M/ && length($value) > 80) {
2674     return 1;
2675   } else {
2676     return 0;
2677   }
2678 }
2679
2680 =item decrypt($value)
2681
2682 Uses the private key to decrypt the string. Returns the decryoted string or undef on failure.
2683
2684 You should generally not have to worry about calling this, as the system handles this for you.
2685
2686 =cut
2687
2688 sub decrypt {
2689   my ($self,$value) = @_;
2690   my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
2691   if ($conf->exists('encryption') && $self->is_encrypted($value)) {
2692     $self->loadRSA;
2693     if (ref($rsa_decrypt) =~ /::RSA/) {
2694       my $encrypted = unpack ("u*", $value);
2695       $decrypted =  unpack("Z*", eval{$rsa_decrypt->decrypt($encrypted)});
2696       if ($@) {warn "Decryption Failed"};
2697     }
2698   }
2699   return $decrypted;
2700 }
2701
2702 sub loadRSA {
2703     my $self = shift;
2704     #Initialize the Module
2705     $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default
2706
2707     if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') {
2708       $rsa_module = $conf->config('encryptionmodule');
2709     }
2710
2711     if (!$rsa_loaded) {
2712         eval ("require $rsa_module"); # No need to import the namespace
2713         $rsa_loaded++;
2714     }
2715     # Initialize Encryption
2716     if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') {
2717       my $public_key = join("\n",$conf->config('encryptionpublickey'));
2718       $rsa_encrypt = $rsa_module->new_public_key($public_key);
2719     }
2720     
2721     # Intitalize Decryption
2722     if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') {
2723       my $private_key = join("\n",$conf->config('encryptionprivatekey'));
2724       $rsa_decrypt = $rsa_module->new_private_key($private_key);
2725     }
2726 }
2727
2728 =item h_search ACTION
2729
2730 Given an ACTION, either "insert", or "delete", returns the appropriate history
2731 record corresponding to this record, if any.
2732
2733 =cut
2734
2735 sub h_search {
2736   my( $self, $action ) = @_;
2737
2738   my $table = $self->table;
2739   $table =~ s/^h_//;
2740
2741   my $primary_key = dbdef->table($table)->primary_key;
2742
2743   qsearchs({
2744     'table'   => "h_$table",
2745     'hashref' => { $primary_key     => $self->$primary_key(),
2746                    'history_action' => $action,
2747                  },
2748   });
2749
2750 }
2751
2752 =item h_date ACTION
2753
2754 Given an ACTION, either "insert", or "delete", returns the timestamp of the
2755 appropriate history record corresponding to this record, if any.
2756
2757 =cut
2758
2759 sub h_date {
2760   my($self, $action) = @_;
2761   my $h = $self->h_search($action);
2762   $h ? $h->history_date : '';
2763 }
2764
2765 =back
2766
2767 =head1 SUBROUTINES
2768
2769 =over 4
2770
2771 =item real_fields [ TABLE ]
2772
2773 Returns a list of the real columns in the specified table.  Called only by 
2774 fields() and other subroutines elsewhere in FS::Record.
2775
2776 =cut
2777
2778 sub real_fields {
2779   my $table = shift;
2780
2781   my($table_obj) = dbdef->table($table);
2782   confess "Unknown table $table" unless $table_obj;
2783   $table_obj->columns;
2784 }
2785
2786 =item _quote VALUE, TABLE, COLUMN
2787
2788 This is an internal function used to construct SQL statements.  It returns
2789 VALUE DBI-quoted (see L<DBI/"quote">) unless VALUE is a number and the column
2790 type (see L<DBIx::DBSchema::Column>) does not end in `char' or `binary'.
2791
2792 =cut
2793
2794 sub _quote {
2795   my($value, $table, $column) = @_;
2796   my $column_obj = dbdef->table($table)->column($column);
2797   my $column_type = $column_obj->type;
2798   my $nullable = $column_obj->null;
2799
2800   warn "  $table.$column: $value ($column_type".
2801        ( $nullable ? ' NULL' : ' NOT NULL' ).
2802        ")\n" if $DEBUG > 2;
2803
2804   if ( $value eq '' && $nullable ) {
2805     'NULL';
2806   } elsif ( $value eq '' && $column_type =~ /^(int|numeric)/ ) {
2807     cluck "WARNING: Attempting to set non-null integer $table.$column null; ".
2808           "using 0 instead";
2809     0;
2810   } elsif ( $value =~ /^\d+(\.\d+)?$/ && 
2811             ! $column_type =~ /(char|binary|text)$/i ) {
2812     $value;
2813   } elsif (( $column_type =~ /^bytea$/i || $column_type =~ /(blob|varbinary)/i )
2814            && driver_name eq 'Pg'
2815           )
2816   {
2817     no strict 'subs';
2818 #    dbh->quote($value, { pg_type => PG_BYTEA() }); # doesn't work right
2819     # Pg binary string quoting: convert each character to 3-digit octal prefixed with \\, 
2820     # single-quote the whole mess, and put an "E" in front.
2821     return ("E'" . join('', map { sprintf('\\\\%03o', ord($_)) } split(//, $value) ) . "'");
2822   } else {
2823     dbh->quote($value);
2824   }
2825 }
2826
2827 =item hfields TABLE
2828
2829 This is deprecated.  Don't use it.
2830
2831 It returns a hash-type list with the fields of this record's table set true.
2832
2833 =cut
2834
2835 sub hfields {
2836   carp "warning: hfields is deprecated";
2837   my($table)=@_;
2838   my(%hash);
2839   foreach (fields($table)) {
2840     $hash{$_}=1;
2841   }
2842   \%hash;
2843 }
2844
2845 sub _dump {
2846   my($self)=@_;
2847   join("\n", map {
2848     "$_: ". $self->getfield($_). "|"
2849   } (fields($self->table)) );
2850 }
2851
2852 sub DESTROY { return; }
2853
2854 #sub DESTROY {
2855 #  my $self = shift;
2856 #  #use Carp qw(cluck);
2857 #  #cluck "DESTROYING $self";
2858 #  warn "DESTROYING $self";
2859 #}
2860
2861 #sub is_tainted {
2862 #             return ! eval { join('',@_), kill 0; 1; };
2863 #         }
2864
2865 =item str2time_sql [ DRIVER_NAME ]
2866
2867 Returns a function to convert to unix time based on database type, such as
2868 "EXTRACT( EPOCH FROM" for Pg or "UNIX_TIMESTAMP(" for mysql.  See
2869 the str2time_sql_closing method to return a closing string rather than just
2870 using a closing parenthesis as previously suggested.
2871
2872 You can pass an optional driver name such as "Pg", "mysql" or
2873 $dbh->{Driver}->{Name} to return a function for that database instead of
2874 the current database.
2875
2876 =cut
2877
2878 sub str2time_sql { 
2879   my $driver = shift || driver_name;
2880
2881   return 'UNIX_TIMESTAMP('      if $driver =~ /^mysql/i;
2882   return 'EXTRACT( EPOCH FROM ' if $driver =~ /^Pg/i;
2883
2884   warn "warning: unknown database type $driver; guessing how to convert ".
2885        "dates to UNIX timestamps";
2886   return 'EXTRACT(EPOCH FROM ';
2887
2888 }
2889
2890 =item str2time_sql_closing [ DRIVER_NAME ]
2891
2892 Returns the closing suffix of a function to convert to unix time based on
2893 database type, such as ")::integer" for Pg or ")" for mysql.
2894
2895 You can pass an optional driver name such as "Pg", "mysql" or
2896 $dbh->{Driver}->{Name} to return a function for that database instead of
2897 the current database.
2898
2899 =cut
2900
2901 sub str2time_sql_closing { 
2902   my $driver = shift || driver_name;
2903
2904   return ' )::INTEGER ' if $driver =~ /^Pg/i;
2905   return ' ) ';
2906 }
2907
2908 =back
2909
2910 =head1 BUGS
2911
2912 This module should probably be renamed, since much of the functionality is
2913 of general use.  It is not completely unlike Adapter::DBI (see below).
2914
2915 Exported qsearch and qsearchs should be deprecated in favor of method calls
2916 (against an FS::Record object like the old search and searchs that qsearch
2917 and qsearchs were on top of.)
2918
2919 The whole fields / hfields mess should be removed.
2920
2921 The various WHERE clauses should be subroutined.
2922
2923 table string should be deprecated in favor of DBIx::DBSchema::Table.
2924
2925 No doubt we could benefit from a Tied hash.  Documenting how exists / defined
2926 true maps to the database (and WHERE clauses) would also help.
2927
2928 The ut_ methods should ask the dbdef for a default length.
2929
2930 ut_sqltype (like ut_varchar) should all be defined
2931
2932 A fallback check method should be provided which uses the dbdef.
2933
2934 The ut_money method assumes money has two decimal digits.
2935
2936 The Pg money kludge in the new method only strips `$'.
2937
2938 The ut_phonen method only checks US-style phone numbers.
2939
2940 The _quote function should probably use ut_float instead of a regex.
2941
2942 All the subroutines probably should be methods, here or elsewhere.
2943
2944 Probably should borrow/use some dbdef methods where appropriate (like sub
2945 fields)
2946
2947 As of 1.14, DBI fetchall_hashref( {} ) doesn't set fetchrow_hashref NAME_lc,
2948 or allow it to be set.  Working around it is ugly any way around - DBI should
2949 be fixed.  (only affects RDBMS which return uppercase column names)
2950
2951 ut_zip should take an optional country like ut_phone.
2952
2953 =head1 SEE ALSO
2954
2955 L<DBIx::DBSchema>, L<FS::UID>, L<DBI>
2956
2957 Adapter::DBI from Ch. 11 of Advanced Perl Programming by Sriram Srinivasan.
2958
2959 http://poop.sf.net/
2960
2961 =cut
2962
2963 1;
2964