fix scalar_sql not to return empty string for zero
[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.38;
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 exists COLUMN
799
800 Returns true if the column/field/key COLUMN exists.
801
802 =cut
803
804 sub exists {
805   my($self,$field) = @_;
806   exists($self->{Hash}->{$field});
807 }
808
809 =item AUTLOADED METHODS
810
811 $record->column is a synonym for $record->get('column');
812
813 $record->column('value') is a synonym for $record->set('column','value');
814
815 =cut
816
817 # readable/safe
818 sub AUTOLOAD {
819   my($self,$value)=@_;
820   my($field)=$AUTOLOAD;
821   $field =~ s/.*://;
822   if ( defined($value) ) {
823     confess "errant AUTOLOAD $field for $self (arg $value)"
824       unless blessed($self) && $self->can('setfield');
825     $self->setfield($field,$value);
826   } else {
827     confess "errant AUTOLOAD $field for $self (no args)"
828       unless blessed($self) && $self->can('getfield');
829     $self->getfield($field);
830   }    
831 }
832
833 # efficient
834 #sub AUTOLOAD {
835 #  my $field = $AUTOLOAD;
836 #  $field =~ s/.*://;
837 #  if ( defined($_[1]) ) {
838 #    $_[0]->setfield($field, $_[1]);
839 #  } else {
840 #    $_[0]->getfield($field);
841 #  }    
842 #}
843
844 =item hash
845
846 Returns a list of the column/value pairs, usually for assigning to a new hash.
847
848 To make a distinct duplicate of an FS::Record object, you can do:
849
850     $new = new FS::Record ( $old->table, { $old->hash } );
851
852 =cut
853
854 sub hash {
855   my($self) = @_;
856   confess $self. ' -> hash: Hash attribute is undefined'
857     unless defined($self->{'Hash'});
858   %{ $self->{'Hash'} }; 
859 }
860
861 =item hashref
862
863 Returns a reference to the column/value hash.  This may be deprecated in the
864 future; if there's a reason you can't just use the autoloaded or get/set
865 methods, speak up.
866
867 =cut
868
869 sub hashref {
870   my($self) = @_;
871   $self->{'Hash'};
872 }
873
874 =item modified
875
876 Returns true if any of this object's values have been modified with set (or via
877 an autoloaded method).  Doesn't yet recognize when you retreive a hashref and
878 modify that.
879
880 =cut
881
882 sub modified {
883   my $self = shift;
884   $self->{'modified'};
885 }
886
887 =item select_for_update
888
889 Selects this record with the SQL "FOR UPDATE" command.  This can be useful as
890 a mutex.
891
892 =cut
893
894 sub select_for_update {
895   my $self = shift;
896   my $primary_key = $self->primary_key;
897   qsearchs( {
898     'select'    => '*',
899     'table'     => $self->table,
900     'hashref'   => { $primary_key => $self->$primary_key() },
901     'extra_sql' => 'FOR UPDATE',
902   } );
903 }
904
905 =item lock_table
906
907 Locks this table with a database-driver specific lock method.  This is used
908 as a mutex in order to do a duplicate search.
909
910 For PostgreSQL, does "LOCK TABLE tablename IN SHARE ROW EXCLUSIVE MODE".
911
912 For MySQL, does a SELECT FOR UPDATE on the duplicate_lock table.
913
914 Errors are fatal; no useful return value.
915
916 Note: To use this method for new tables other than svc_acct and svc_phone,
917 edit freeside-upgrade and add those tables to the duplicate_lock list.
918
919 =cut
920
921 sub lock_table {
922   my $self = shift;
923   my $table = $self->table;
924
925   warn "$me locking $table table\n" if $DEBUG;
926
927   if ( driver_name =~ /^Pg/i ) {
928
929     dbh->do("LOCK TABLE $table IN SHARE ROW EXCLUSIVE MODE")
930       or die dbh->errstr;
931
932   } elsif ( driver_name =~ /^mysql/i ) {
933
934     dbh->do("SELECT * FROM duplicate_lock
935                WHERE lockname = '$table'
936                FOR UPDATE"
937            ) or die dbh->errstr;
938
939   } else {
940
941     die "unknown database ". driver_name. "; don't know how to lock table";
942
943   }
944
945   warn "$me acquired $table table lock\n" if $DEBUG;
946
947 }
948
949 =item insert
950
951 Inserts this record to the database.  If there is an error, returns the error,
952 otherwise returns false.
953
954 =cut
955
956 sub insert {
957   my $self = shift;
958   my $saved = {};
959
960   warn "$self -> insert" if $DEBUG;
961
962   my $error = $self->check;
963   return $error if $error;
964
965   #single-field unique keys are given a value if false
966   #(like MySQL's AUTO_INCREMENT or Pg SERIAL)
967   foreach ( $self->dbdef_table->unique_singles) {
968     $self->unique($_) unless $self->getfield($_);
969   }
970
971   #and also the primary key, if the database isn't going to
972   my $primary_key = $self->dbdef_table->primary_key;
973   my $db_seq = 0;
974   if ( $primary_key ) {
975     my $col = $self->dbdef_table->column($primary_key);
976
977     $db_seq =
978       uc($col->type) =~ /^(BIG)?SERIAL\d?/
979       || ( driver_name eq 'Pg'
980              && defined($col->default)
981              && $col->quoted_default =~ /^nextval\(/i
982          )
983       || ( driver_name eq 'mysql'
984              && defined($col->local)
985              && $col->local =~ /AUTO_INCREMENT/i
986          );
987     $self->unique($primary_key) unless $self->getfield($primary_key) || $db_seq;
988   }
989
990   my $table = $self->table;
991   
992   # Encrypt before the database
993   if (    defined(eval '@FS::'. $table . '::encrypted_fields')
994        && scalar( eval '@FS::'. $table . '::encrypted_fields')
995        && $conf->exists('encryption')
996   ) {
997     foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
998       $self->{'saved'} = $self->getfield($field);
999       $self->setfield($field, $self->encrypt($self->getfield($field)));
1000     }
1001   }
1002
1003   #false laziness w/delete
1004   my @real_fields =
1005     grep { defined($self->getfield($_)) && $self->getfield($_) ne "" }
1006     real_fields($table)
1007   ;
1008   my @values = map { _quote( $self->getfield($_), $table, $_) } @real_fields;
1009   #eslaf
1010
1011   my $statement = "INSERT INTO $table ";
1012   if ( @real_fields ) {
1013     $statement .=
1014       "( ".
1015         join( ', ', @real_fields ).
1016       ") VALUES (".
1017         join( ', ', @values ).
1018        ")"
1019     ;
1020   } else {
1021     $statement .= 'DEFAULT VALUES';
1022   }
1023   warn "[debug]$me $statement\n" if $DEBUG > 1;
1024   my $sth = dbh->prepare($statement) or return dbh->errstr;
1025
1026   local $SIG{HUP} = 'IGNORE';
1027   local $SIG{INT} = 'IGNORE';
1028   local $SIG{QUIT} = 'IGNORE'; 
1029   local $SIG{TERM} = 'IGNORE';
1030   local $SIG{TSTP} = 'IGNORE';
1031   local $SIG{PIPE} = 'IGNORE';
1032
1033   $sth->execute or return $sth->errstr;
1034
1035   # get inserted id from the database, if applicable & needed
1036   if ( $db_seq && ! $self->getfield($primary_key) ) {
1037     warn "[debug]$me retreiving sequence from database\n" if $DEBUG;
1038   
1039     my $insertid = '';
1040
1041     if ( driver_name eq 'Pg' ) {
1042
1043       #my $oid = $sth->{'pg_oid_status'};
1044       #my $i_sql = "SELECT $primary_key FROM $table WHERE oid = ?";
1045
1046       my $default = $self->dbdef_table->column($primary_key)->quoted_default;
1047       unless ( $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i ) {
1048         dbh->rollback if $FS::UID::AutoCommit;
1049         return "can't parse $table.$primary_key default value".
1050                " for sequence name: $default";
1051       }
1052       my $sequence = $1;
1053
1054       my $i_sql = "SELECT currval('$sequence')";
1055       my $i_sth = dbh->prepare($i_sql) or do {
1056         dbh->rollback if $FS::UID::AutoCommit;
1057         return dbh->errstr;
1058       };
1059       $i_sth->execute() or do { #$i_sth->execute($oid)
1060         dbh->rollback if $FS::UID::AutoCommit;
1061         return $i_sth->errstr;
1062       };
1063       $insertid = $i_sth->fetchrow_arrayref->[0];
1064
1065     } elsif ( driver_name eq 'mysql' ) {
1066
1067       $insertid = dbh->{'mysql_insertid'};
1068       # work around mysql_insertid being null some of the time, ala RT :/
1069       unless ( $insertid ) {
1070         warn "WARNING: DBD::mysql didn't return mysql_insertid; ".
1071              "using SELECT LAST_INSERT_ID();";
1072         my $i_sql = "SELECT LAST_INSERT_ID()";
1073         my $i_sth = dbh->prepare($i_sql) or do {
1074           dbh->rollback if $FS::UID::AutoCommit;
1075           return dbh->errstr;
1076         };
1077         $i_sth->execute or do {
1078           dbh->rollback if $FS::UID::AutoCommit;
1079           return $i_sth->errstr;
1080         };
1081         $insertid = $i_sth->fetchrow_arrayref->[0];
1082       }
1083
1084     } else {
1085
1086       dbh->rollback if $FS::UID::AutoCommit;
1087       return "don't know how to retreive inserted ids from ". driver_name. 
1088              ", try using counterfiles (maybe run dbdef-create?)";
1089
1090     }
1091
1092     $self->setfield($primary_key, $insertid);
1093
1094   }
1095
1096   my @virtual_fields = 
1097       grep defined($self->getfield($_)) && $self->getfield($_) ne "",
1098           $self->virtual_fields;
1099   if (@virtual_fields) {
1100     my %v_values = map { $_, $self->getfield($_) } @virtual_fields;
1101
1102     my $vfieldpart = $self->vfieldpart_hashref;
1103
1104     my $v_statement = "INSERT INTO virtual_field(recnum, vfieldpart, value) ".
1105                     "VALUES (?, ?, ?)";
1106
1107     my $v_sth = dbh->prepare($v_statement) or do {
1108       dbh->rollback if $FS::UID::AutoCommit;
1109       return dbh->errstr;
1110     };
1111
1112     foreach (keys(%v_values)) {
1113       $v_sth->execute($self->getfield($primary_key),
1114                       $vfieldpart->{$_},
1115                       $v_values{$_})
1116       or do {
1117         dbh->rollback if $FS::UID::AutoCommit;
1118         return $v_sth->errstr;
1119       };
1120     }
1121   }
1122
1123
1124   my $h_sth;
1125   if ( defined dbdef->table('h_'. $table) ) {
1126     my $h_statement = $self->_h_statement('insert');
1127     warn "[debug]$me $h_statement\n" if $DEBUG > 2;
1128     $h_sth = dbh->prepare($h_statement) or do {
1129       dbh->rollback if $FS::UID::AutoCommit;
1130       return dbh->errstr;
1131     };
1132   } else {
1133     $h_sth = '';
1134   }
1135   $h_sth->execute or return $h_sth->errstr if $h_sth;
1136
1137   dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
1138
1139   # Now that it has been saved, reset the encrypted fields so that $new 
1140   # can still be used.
1141   foreach my $field (keys %{$saved}) {
1142     $self->setfield($field, $saved->{$field});
1143   }
1144
1145   '';
1146 }
1147
1148 =item add
1149
1150 Depriciated (use insert instead).
1151
1152 =cut
1153
1154 sub add {
1155   cluck "warning: FS::Record::add deprecated!";
1156   insert @_; #call method in this scope
1157 }
1158
1159 =item delete
1160
1161 Delete this record from the database.  If there is an error, returns the error,
1162 otherwise returns false.
1163
1164 =cut
1165
1166 sub delete {
1167   my $self = shift;
1168
1169   my $statement = "DELETE FROM ". $self->table. " WHERE ". join(' AND ',
1170     map {
1171       $self->getfield($_) eq ''
1172         #? "( $_ IS NULL OR $_ = \"\" )"
1173         ? ( driver_name eq 'Pg'
1174               ? "$_ IS NULL"
1175               : "( $_ IS NULL OR $_ = \"\" )"
1176           )
1177         : "$_ = ". _quote($self->getfield($_),$self->table,$_)
1178     } ( $self->dbdef_table->primary_key )
1179           ? ( $self->dbdef_table->primary_key)
1180           : real_fields($self->table)
1181   );
1182   warn "[debug]$me $statement\n" if $DEBUG > 1;
1183   my $sth = dbh->prepare($statement) or return dbh->errstr;
1184
1185   my $h_sth;
1186   if ( defined dbdef->table('h_'. $self->table) ) {
1187     my $h_statement = $self->_h_statement('delete');
1188     warn "[debug]$me $h_statement\n" if $DEBUG > 2;
1189     $h_sth = dbh->prepare($h_statement) or return dbh->errstr;
1190   } else {
1191     $h_sth = '';
1192   }
1193
1194   my $primary_key = $self->dbdef_table->primary_key;
1195   my $v_sth;
1196   my @del_vfields;
1197   my $vfp = $self->vfieldpart_hashref;
1198   foreach($self->virtual_fields) {
1199     next if $self->getfield($_) eq '';
1200     unless(@del_vfields) {
1201       my $st = "DELETE FROM virtual_field WHERE recnum = ? AND vfieldpart = ?";
1202       $v_sth = dbh->prepare($st) or return dbh->errstr;
1203     }
1204     push @del_vfields, $_;
1205   }
1206
1207   local $SIG{HUP} = 'IGNORE';
1208   local $SIG{INT} = 'IGNORE';
1209   local $SIG{QUIT} = 'IGNORE'; 
1210   local $SIG{TERM} = 'IGNORE';
1211   local $SIG{TSTP} = 'IGNORE';
1212   local $SIG{PIPE} = 'IGNORE';
1213
1214   my $rc = $sth->execute or return $sth->errstr;
1215   #not portable #return "Record not found, statement:\n$statement" if $rc eq "0E0";
1216   $h_sth->execute or return $h_sth->errstr if $h_sth;
1217   $v_sth->execute($self->getfield($primary_key), $vfp->{$_}) 
1218     or return $v_sth->errstr 
1219         foreach (@del_vfields);
1220   
1221   dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
1222
1223   #no need to needlessly destoy the data either (causes problems actually)
1224   #undef $self; #no need to keep object!
1225
1226   '';
1227 }
1228
1229 =item del
1230
1231 Depriciated (use delete instead).
1232
1233 =cut
1234
1235 sub del {
1236   cluck "warning: FS::Record::del deprecated!";
1237   &delete(@_); #call method in this scope
1238 }
1239
1240 =item replace OLD_RECORD
1241
1242 Replace the OLD_RECORD with this one in the database.  If there is an error,
1243 returns the error, otherwise returns false.
1244
1245 =cut
1246
1247 sub replace {
1248   my ($new, $old) = (shift, shift);
1249
1250   $old = $new->replace_old unless defined($old);
1251
1252   warn "[debug]$me $new ->replace $old\n" if $DEBUG;
1253
1254   if ( $new->can('replace_check') ) {
1255     my $error = $new->replace_check($old);
1256     return $error if $error;
1257   }
1258
1259   return "Records not in same table!" unless $new->table eq $old->table;
1260
1261   my $primary_key = $old->dbdef_table->primary_key;
1262   return "Can't change primary key $primary_key ".
1263          'from '. $old->getfield($primary_key).
1264          ' to ' . $new->getfield($primary_key)
1265     if $primary_key
1266        && ( $old->getfield($primary_key) ne $new->getfield($primary_key) );
1267
1268   my $error = $new->check;
1269   return $error if $error;
1270   
1271   # Encrypt for replace
1272   my $saved = {};
1273   if (    $conf->exists('encryption')
1274        && defined(eval '@FS::'. $new->table . '::encrypted_fields')
1275        && scalar( eval '@FS::'. $new->table . '::encrypted_fields')
1276   ) {
1277     foreach my $field (eval '@FS::'. $new->table . '::encrypted_fields') {
1278       $saved->{$field} = $new->getfield($field);
1279       $new->setfield($field, $new->encrypt($new->getfield($field)));
1280     }
1281   }
1282
1283   #my @diff = grep $new->getfield($_) ne $old->getfield($_), $old->fields;
1284   my %diff = map { ($new->getfield($_) ne $old->getfield($_))
1285                    ? ($_, $new->getfield($_)) : () } $old->fields;
1286                    
1287   unless (keys(%diff) || $no_update_diff ) {
1288     carp "[warning]$me $new -> replace $old: records identical"
1289       unless $nowarn_identical;
1290     return '';
1291   }
1292
1293   my $statement = "UPDATE ". $old->table. " SET ". join(', ',
1294     map {
1295       "$_ = ". _quote($new->getfield($_),$old->table,$_) 
1296     } real_fields($old->table)
1297   ). ' WHERE '.
1298     join(' AND ',
1299       map {
1300
1301         if ( $old->getfield($_) eq '' ) {
1302
1303          #false laziness w/qsearch
1304          if ( driver_name eq 'Pg' ) {
1305             my $type = $old->dbdef_table->column($_)->type;
1306             if ( $type =~ /(int|(big)?serial)/i ) {
1307               qq-( $_ IS NULL )-;
1308             } else {
1309               qq-( $_ IS NULL OR $_ = '' )-;
1310             }
1311           } else {
1312             qq-( $_ IS NULL OR $_ = "" )-;
1313           }
1314
1315         } else {
1316           "$_ = ". _quote($old->getfield($_),$old->table,$_);
1317         }
1318
1319       } ( $primary_key ? ( $primary_key ) : real_fields($old->table) )
1320     )
1321   ;
1322   warn "[debug]$me $statement\n" if $DEBUG > 1;
1323   my $sth = dbh->prepare($statement) or return dbh->errstr;
1324
1325   my $h_old_sth;
1326   if ( defined dbdef->table('h_'. $old->table) ) {
1327     my $h_old_statement = $old->_h_statement('replace_old');
1328     warn "[debug]$me $h_old_statement\n" if $DEBUG > 2;
1329     $h_old_sth = dbh->prepare($h_old_statement) or return dbh->errstr;
1330   } else {
1331     $h_old_sth = '';
1332   }
1333
1334   my $h_new_sth;
1335   if ( defined dbdef->table('h_'. $new->table) ) {
1336     my $h_new_statement = $new->_h_statement('replace_new');
1337     warn "[debug]$me $h_new_statement\n" if $DEBUG > 2;
1338     $h_new_sth = dbh->prepare($h_new_statement) or return dbh->errstr;
1339   } else {
1340     $h_new_sth = '';
1341   }
1342
1343   # For virtual fields we have three cases with different SQL 
1344   # statements: add, replace, delete
1345   my $v_add_sth;
1346   my $v_rep_sth;
1347   my $v_del_sth;
1348   my (@add_vfields, @rep_vfields, @del_vfields);
1349   my $vfp = $old->vfieldpart_hashref;
1350   foreach(grep { exists($diff{$_}) } $new->virtual_fields) {
1351     if($diff{$_} eq '') {
1352       # Delete
1353       unless(@del_vfields) {
1354         my $st = "DELETE FROM virtual_field WHERE recnum = ? ".
1355                  "AND vfieldpart = ?";
1356         warn "[debug]$me $st\n" if $DEBUG > 2;
1357         $v_del_sth = dbh->prepare($st) or return dbh->errstr;
1358       }
1359       push @del_vfields, $_;
1360     } elsif($old->getfield($_) eq '') {
1361       # Add
1362       unless(@add_vfields) {
1363         my $st = "INSERT INTO virtual_field (value, recnum, vfieldpart) ".
1364                  "VALUES (?, ?, ?)";
1365         warn "[debug]$me $st\n" if $DEBUG > 2;
1366         $v_add_sth = dbh->prepare($st) or return dbh->errstr;
1367       }
1368       push @add_vfields, $_;
1369     } else {
1370       # Replace
1371       unless(@rep_vfields) {
1372         my $st = "UPDATE virtual_field SET value = ? ".
1373                  "WHERE recnum = ? AND vfieldpart = ?";
1374         warn "[debug]$me $st\n" if $DEBUG > 2;
1375         $v_rep_sth = dbh->prepare($st) or return dbh->errstr;
1376       }
1377       push @rep_vfields, $_;
1378     }
1379   }
1380
1381   local $SIG{HUP} = 'IGNORE';
1382   local $SIG{INT} = 'IGNORE';
1383   local $SIG{QUIT} = 'IGNORE'; 
1384   local $SIG{TERM} = 'IGNORE';
1385   local $SIG{TSTP} = 'IGNORE';
1386   local $SIG{PIPE} = 'IGNORE';
1387
1388   my $rc = $sth->execute or return $sth->errstr;
1389   #not portable #return "Record not found (or records identical)." if $rc eq "0E0";
1390   $h_old_sth->execute or return $h_old_sth->errstr if $h_old_sth;
1391   $h_new_sth->execute or return $h_new_sth->errstr if $h_new_sth;
1392
1393   $v_del_sth->execute($old->getfield($primary_key),
1394                       $vfp->{$_})
1395         or return $v_del_sth->errstr
1396       foreach(@del_vfields);
1397
1398   $v_add_sth->execute($new->getfield($_),
1399                       $old->getfield($primary_key),
1400                       $vfp->{$_})
1401         or return $v_add_sth->errstr
1402       foreach(@add_vfields);
1403
1404   $v_rep_sth->execute($new->getfield($_),
1405                       $old->getfield($primary_key),
1406                       $vfp->{$_})
1407         or return $v_rep_sth->errstr
1408       foreach(@rep_vfields);
1409
1410   dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
1411
1412   # Now that it has been saved, reset the encrypted fields so that $new 
1413   # can still be used.
1414   foreach my $field (keys %{$saved}) {
1415     $new->setfield($field, $saved->{$field});
1416   }
1417
1418   '';
1419
1420 }
1421
1422 sub replace_old {
1423   my( $self ) = shift;
1424   warn "[$me] replace called with no arguments; autoloading old record\n"
1425     if $DEBUG;
1426
1427   my $primary_key = $self->dbdef_table->primary_key;
1428   if ( $primary_key ) {
1429     $self->by_key( $self->$primary_key() ) #this is what's returned
1430       or croak "can't find ". $self->table. ".$primary_key ".
1431         $self->$primary_key();
1432   } else {
1433     croak $self->table. " has no primary key; pass old record as argument";
1434   }
1435
1436 }
1437
1438 =item rep
1439
1440 Depriciated (use replace instead).
1441
1442 =cut
1443
1444 sub rep {
1445   cluck "warning: FS::Record::rep deprecated!";
1446   replace @_; #call method in this scope
1447 }
1448
1449 =item check
1450
1451 Checks virtual fields (using check_blocks).  Subclasses should still provide 
1452 a check method to validate real fields, foreign keys, etc., and call this 
1453 method via $self->SUPER::check.
1454
1455 (FIXME: Should this method try to make sure that it I<is> being called from 
1456 a subclass's check method, to keep the current semantics as far as possible?)
1457
1458 =cut
1459
1460 sub check {
1461   #confess "FS::Record::check not implemented; supply one in subclass!";
1462   my $self = shift;
1463
1464   foreach my $field ($self->virtual_fields) {
1465     for ($self->getfield($field)) {
1466       # See notes on check_block in FS::part_virtual_field.
1467       eval $self->pvf($field)->check_block;
1468       if ( $@ ) {
1469         #this is bad, probably want to follow the stack backtrace up and see
1470         #wtf happened
1471         my $err = "Fatal error checking $field for $self";
1472         cluck "$err: $@";
1473         return "$err (see log for backtrace): $@";
1474
1475       }
1476       $self->setfield($field, $_);
1477     }
1478   }
1479   '';
1480 }
1481
1482 =item process_batch_import JOB OPTIONS_HASHREF PARAMS
1483
1484 Processes a batch import as a queued JSRPC job
1485
1486 JOB is an FS::queue entry.
1487
1488 OPTIONS_HASHREF can have the following keys:
1489
1490 =over 4
1491
1492 =item table
1493
1494 Table name (required).
1495
1496 =item params
1497
1498 Listref of field names for static fields.  They will be given values from the
1499 PARAMS hashref and passed as a "params" hashref to batch_import.
1500
1501 =item formats
1502
1503 Formats hashref.  Keys are field names, values are listrefs that define the
1504 format.
1505
1506 Each listref value can be a column name or a code reference.  Coderefs are run
1507 with the row object, data and a FS::Conf object as the three parameters.
1508 For example, this coderef does the same thing as using the "columnname" string:
1509
1510   sub {
1511     my( $record, $data, $conf ) = @_;
1512     $record->columnname( $data );
1513   },
1514
1515 Coderefs are run after all "column name" fields are assigned.
1516
1517 =item format_types
1518
1519 Optional format hashref of types.  Keys are field names, values are "csv",
1520 "xls" or "fixedlength".  Overrides automatic determination of file type
1521 from extension.
1522
1523 =item format_headers
1524
1525 Optional format hashref of header lines.  Keys are field names, values are 0
1526 for no header, 1 to ignore the first line, or to higher numbers to ignore that
1527 number of lines.
1528
1529 =item format_sep_chars
1530
1531 Optional format hashref of CSV sep_chars.  Keys are field names, values are the
1532 CSV separation character.
1533
1534 =item format_fixedlenth_formats
1535
1536 Optional format hashref of fixed length format defintiions.  Keys are field
1537 names, values Parse::FixedLength listrefs of field definitions.
1538
1539 =item default_csv
1540
1541 Set true to default to CSV file type if the filename does not contain a
1542 recognizable ".csv" or ".xls" extension (and type is not pre-specified by
1543 format_types).
1544
1545 =back
1546
1547 PARAMS is a base64-encoded Storable string containing the POSTed data as
1548 a hash ref.  It normally contains at least one field, "uploaded files",
1549 generated by /elements/file-upload.html and containing the list of uploaded
1550 files.  Currently only supports a single file named "file".
1551
1552 =cut
1553
1554 use Storable qw(thaw);
1555 use Data::Dumper;
1556 use MIME::Base64;
1557 sub process_batch_import {
1558   my($job, $opt) = ( shift, shift );
1559
1560   my $table = $opt->{table};
1561   my @pass_params = $opt->{params} ? @{ $opt->{params} } : ();
1562   my %formats = %{ $opt->{formats} };
1563
1564   my $param = thaw(decode_base64(shift));
1565   warn Dumper($param) if $DEBUG;
1566   
1567   my $files = $param->{'uploaded_files'}
1568     or die "No files provided.\n";
1569
1570   my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
1571
1572   my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
1573   my $file = $dir. $files{'file'};
1574
1575   my %iopt = (
1576     #class-static
1577     table                      => $table,
1578     formats                    => \%formats,
1579     format_types               => $opt->{format_types},
1580     format_headers             => $opt->{format_headers},
1581     format_sep_chars           => $opt->{format_sep_chars},
1582     format_fixedlength_formats => $opt->{format_fixedlength_formats},
1583     #per-import
1584     job                        => $job,
1585     file                       => $file,
1586     #type                       => $type,
1587     format                     => $param->{format},
1588     params                     => { map { $_ => $param->{$_} } @pass_params },
1589     #?
1590     default_csv                => $opt->{default_csv},
1591   );
1592
1593   if ( $opt->{'batch_namecol'} ) {
1594     $iopt{'batch_namevalue'} = $param->{ $opt->{'batch_namecol'} };
1595     $iopt{$_} = $opt->{$_} foreach qw( batch_keycol batch_table batch_namecol );
1596   }
1597
1598   my $error = FS::Record::batch_import( \%iopt );
1599
1600   unlink $file;
1601
1602   die "$error\n" if $error;
1603 }
1604
1605 =item batch_import PARAM_HASHREF
1606
1607 Class method for batch imports.  Available params:
1608
1609 =over 4
1610
1611 =item table
1612
1613 =item formats
1614
1615 =item format_types
1616
1617 =item format_headers
1618
1619 =item format_sep_chars
1620
1621 =item format_fixedlength_formats
1622
1623 =item params
1624
1625 =item job
1626
1627 FS::queue object, will be updated with progress
1628
1629 =item file
1630
1631 =item type
1632
1633 csv, xls or fixedlength
1634
1635 =item format
1636
1637 =item empty_ok
1638
1639 =back
1640
1641 =cut
1642
1643 sub batch_import {
1644   my $param = shift;
1645
1646   warn "$me batch_import call with params: \n". Dumper($param)
1647     if $DEBUG;
1648
1649   my $table   = $param->{table};
1650   my $formats = $param->{formats};
1651
1652   my $job     = $param->{job};
1653   my $file    = $param->{file};
1654   my $format  = $param->{'format'};
1655   my $params  = $param->{params} || {};
1656
1657   die "unknown format $format" unless exists $formats->{ $format };
1658
1659   my $type = $param->{'format_types'}
1660              ? $param->{'format_types'}{ $format }
1661              : $param->{type} || 'csv';
1662
1663   unless ( $type ) {
1664     if ( $file =~ /\.(\w+)$/i ) {
1665       $type = lc($1);
1666     } else {
1667       #or error out???
1668       warn "can't parse file type from filename $file; defaulting to CSV";
1669       $type = 'csv';
1670     }
1671     $type = 'csv'
1672       if $param->{'default_csv'} && $type ne 'xls';
1673   }
1674
1675   my $header = $param->{'format_headers'}
1676                  ? $param->{'format_headers'}{ $param->{'format'} }
1677                  : 0;
1678
1679   my $sep_char = $param->{'format_sep_chars'}
1680                    ? $param->{'format_sep_chars'}{ $param->{'format'} }
1681                    : ',';
1682
1683   my $fixedlength_format =
1684     $param->{'format_fixedlength_formats'}
1685       ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
1686       : '';
1687
1688   my @fields = @{ $formats->{ $format } };
1689
1690   my $row = 0;
1691   my $count;
1692   my $parser;
1693   my @buffer = ();
1694   if ( $type eq 'csv' || $type eq 'fixedlength' ) {
1695
1696     if ( $type eq 'csv' ) {
1697
1698       my %attr = ();
1699       $attr{sep_char} = $sep_char if $sep_char;
1700       $parser = new Text::CSV_XS \%attr;
1701
1702     } elsif ( $type eq 'fixedlength' ) {
1703
1704       eval "use Parse::FixedLength;";
1705       die $@ if $@;
1706       $parser = new Parse::FixedLength $fixedlength_format;
1707  
1708     } else {
1709       die "Unknown file type $type\n";
1710     }
1711
1712     @buffer = split(/\r?\n/, slurp($file) );
1713     splice(@buffer, 0, ($header || 0) );
1714     $count = scalar(@buffer);
1715
1716   } elsif ( $type eq 'xls' ) {
1717
1718     eval "use Spreadsheet::ParseExcel;";
1719     die $@ if $@;
1720
1721     eval "use DateTime::Format::Excel;";
1722     #for now, just let the error be thrown if it is used, since only CDR
1723     # formats bill_west and troop use it, not other excel-parsing things
1724     #die $@ if $@;
1725
1726     my $excel = Spreadsheet::ParseExcel::Workbook->new->Parse($file);
1727
1728     $parser = $excel->{Worksheet}[0]; #first sheet
1729
1730     $count = $parser->{MaxRow} || $parser->{MinRow};
1731     $count++;
1732
1733     $row = $header || 0;
1734
1735   } else {
1736     die "Unknown file type $type\n";
1737   }
1738
1739   #my $columns;
1740
1741   local $SIG{HUP} = 'IGNORE';
1742   local $SIG{INT} = 'IGNORE';
1743   local $SIG{QUIT} = 'IGNORE';
1744   local $SIG{TERM} = 'IGNORE';
1745   local $SIG{TSTP} = 'IGNORE';
1746   local $SIG{PIPE} = 'IGNORE';
1747
1748   my $oldAutoCommit = $FS::UID::AutoCommit;
1749   local $FS::UID::AutoCommit = 0;
1750   my $dbh = dbh;
1751
1752   if ( $param->{'batch_namecol'} && $param->{'batch_namevalue'} ) {
1753     my $batch_col   = $param->{'batch_keycol'};
1754
1755     my $batch_class = 'FS::'. $param->{'batch_table'};
1756     my $batch = $batch_class->new({
1757       $param->{'batch_namecol'} => $param->{'batch_namevalue'}
1758     });
1759     my $error = $batch->insert;
1760     if ( $error ) {
1761       $dbh->rollback if $oldAutoCommit;
1762       return "can't insert batch record: $error";
1763     }
1764     #primary key via dbdef? (so the column names don't have to match)
1765     my $batch_value = $batch->get( $param->{'batch_keycol'} );
1766
1767     $params->{ $batch_col } = $batch_value;
1768   }
1769   
1770   my $line;
1771   my $imported = 0;
1772   my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
1773   while (1) {
1774
1775     my @columns = ();
1776     if ( $type eq 'csv' ) {
1777
1778       last unless scalar(@buffer);
1779       $line = shift(@buffer);
1780
1781       next if $line =~ /^\s*$/; #skip empty lines
1782
1783       $parser->parse($line) or do {
1784         $dbh->rollback if $oldAutoCommit;
1785         return "can't parse: ". $parser->error_input();
1786       };
1787       @columns = $parser->fields();
1788
1789     } elsif ( $type eq 'fixedlength' ) {
1790
1791       @columns = $parser->parse($line);
1792
1793     } elsif ( $type eq 'xls' ) {
1794
1795       last if $row > ($parser->{MaxRow} || $parser->{MinRow})
1796            || ! $parser->{Cells}[$row];
1797
1798       my @row = @{ $parser->{Cells}[$row] };
1799       @columns = map $_->{Val}, @row;
1800
1801       #my $z = 'A';
1802       #warn $z++. ": $_\n" for @columns;
1803
1804     } else {
1805       die "Unknown file type $type\n";
1806     }
1807
1808     my @later = ();
1809     my %hash = %$params;
1810
1811     foreach my $field ( @fields ) {
1812
1813       my $value = shift @columns;
1814      
1815       if ( ref($field) eq 'CODE' ) {
1816         #&{$field}(\%hash, $value);
1817         push @later, $field, $value;
1818       } else {
1819         #??? $hash{$field} = $value if length($value);
1820         $hash{$field} = $value if defined($value) && length($value);
1821       }
1822
1823     }
1824
1825     my $class = "FS::$table";
1826
1827     my $record = $class->new( \%hash );
1828
1829     my $param = {};
1830     while ( scalar(@later) ) {
1831       my $sub = shift @later;
1832       my $data = shift @later;
1833       &{$sub}($record, $data, $conf, $param); # $record->&{$sub}($data, $conf);
1834       last if exists( $param->{skiprow} );
1835     }
1836     next if exists( $param->{skiprow} );
1837
1838     my $error = $record->insert;
1839
1840     if ( $error ) {
1841       $dbh->rollback if $oldAutoCommit;
1842       return "can't insert record". ( $line ? " for $line" : '' ). ": $error";
1843     }
1844
1845     $row++;
1846     $imported++;
1847
1848     if ( $job && time - $min_sec > $last ) { #progress bar
1849       $job->update_statustext( int(100 * $imported / $count) );
1850       $last = time;
1851     }
1852
1853   }
1854
1855   $dbh->commit or die $dbh->errstr if $oldAutoCommit;;
1856
1857   return "Empty file!" unless $imported || $param->{empty_ok};
1858
1859   ''; #no error
1860
1861 }
1862
1863 sub _h_statement {
1864   my( $self, $action, $time ) = @_;
1865
1866   $time ||= time;
1867
1868   my %nohistory = map { $_=>1 } $self->nohistory_fields;
1869
1870   my @fields =
1871     grep { defined($self->get($_)) && $self->get($_) ne "" && ! $nohistory{$_} }
1872     real_fields($self->table);
1873   ;
1874
1875   # If we're encrypting then don't store the payinfo in the history
1876   if ( $conf && $conf->exists('encryption') ) {
1877     @fields = grep { $_ ne 'payinfo' } @fields;
1878   }
1879
1880   my @values = map { _quote( $self->getfield($_), $self->table, $_) } @fields;
1881
1882   "INSERT INTO h_". $self->table. " ( ".
1883       join(', ', qw(history_date history_user history_action), @fields ).
1884     ") VALUES (".
1885       join(', ', $time, dbh->quote(getotaker()), dbh->quote($action), @values).
1886     ")"
1887   ;
1888 }
1889
1890 =item unique COLUMN
1891
1892 B<Warning>: External use is B<deprecated>.  
1893
1894 Replaces COLUMN in record with a unique number, using counters in the
1895 filesystem.  Used by the B<insert> method on single-field unique columns
1896 (see L<DBIx::DBSchema::Table>) and also as a fallback for primary keys
1897 that aren't SERIAL (Pg) or AUTO_INCREMENT (mysql).
1898
1899 Returns the new value.
1900
1901 =cut
1902
1903 sub unique {
1904   my($self,$field) = @_;
1905   my($table)=$self->table;
1906
1907   croak "Unique called on field $field, but it is ",
1908         $self->getfield($field),
1909         ", not null!"
1910     if $self->getfield($field);
1911
1912   #warn "table $table is tainted" if is_tainted($table);
1913   #warn "field $field is tainted" if is_tainted($field);
1914
1915   my($counter) = new File::CounterFile "$table.$field",0;
1916 # hack for web demo
1917 #  getotaker() =~ /^([\w\-]{1,16})$/ or die "Illegal CGI REMOTE_USER!";
1918 #  my($user)=$1;
1919 #  my($counter) = new File::CounterFile "$user/$table.$field",0;
1920 # endhack
1921
1922   my $index = $counter->inc;
1923   $index = $counter->inc while qsearchs($table, { $field=>$index } );
1924
1925   $index =~ /^(\d*)$/;
1926   $index=$1;
1927
1928   $self->setfield($field,$index);
1929
1930 }
1931
1932 =item ut_float COLUMN
1933
1934 Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May not be
1935 null.  If there is an error, returns the error, otherwise returns false.
1936
1937 =cut
1938
1939 sub ut_float {
1940   my($self,$field)=@_ ;
1941   ($self->getfield($field) =~ /^\s*(\d+\.\d+)\s*$/ ||
1942    $self->getfield($field) =~ /^\s*(\d+)\s*$/ ||
1943    $self->getfield($field) =~ /^\s*(\d+\.\d+e\d+)\s*$/ ||
1944    $self->getfield($field) =~ /^\s*(\d+e\d+)\s*$/)
1945     or return "Illegal or empty (float) $field: ". $self->getfield($field);
1946   $self->setfield($field,$1);
1947   '';
1948 }
1949 =item ut_floatn COLUMN
1950
1951 Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May be
1952 null.  If there is an error, returns the error, otherwise returns false.
1953
1954 =cut
1955
1956 #false laziness w/ut_ipn
1957 sub ut_floatn {
1958   my( $self, $field ) = @_;
1959   if ( $self->getfield($field) =~ /^()$/ ) {
1960     $self->setfield($field,'');
1961     '';
1962   } else {
1963     $self->ut_float($field);
1964   }
1965 }
1966
1967 =item ut_sfloat COLUMN
1968
1969 Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10.
1970 May not be null.  If there is an error, returns the error, otherwise returns
1971 false.
1972
1973 =cut
1974
1975 sub ut_sfloat {
1976   my($self,$field)=@_ ;
1977   ($self->getfield($field) =~ /^\s*(-?\d+\.\d+)\s*$/ ||
1978    $self->getfield($field) =~ /^\s*(-?\d+)\s*$/ ||
1979    $self->getfield($field) =~ /^\s*(-?\d+\.\d+[eE]-?\d+)\s*$/ ||
1980    $self->getfield($field) =~ /^\s*(-?\d+[eE]-?\d+)\s*$/)
1981     or return "Illegal or empty (float) $field: ". $self->getfield($field);
1982   $self->setfield($field,$1);
1983   '';
1984 }
1985 =item ut_sfloatn COLUMN
1986
1987 Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10.  May be
1988 null.  If there is an error, returns the error, otherwise returns false.
1989
1990 =cut
1991
1992 sub ut_sfloatn {
1993   my( $self, $field ) = @_;
1994   if ( $self->getfield($field) =~ /^()$/ ) {
1995     $self->setfield($field,'');
1996     '';
1997   } else {
1998     $self->ut_sfloat($field);
1999   }
2000 }
2001
2002 =item ut_snumber COLUMN
2003
2004 Check/untaint signed numeric data (whole numbers).  If there is an error,
2005 returns the error, otherwise returns false.
2006
2007 =cut
2008
2009 sub ut_snumber {
2010   my($self, $field) = @_;
2011   $self->getfield($field) =~ /^\s*(-?)\s*(\d+)\s*$/
2012     or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
2013   $self->setfield($field, "$1$2");
2014   '';
2015 }
2016
2017 =item ut_snumbern COLUMN
2018
2019 Check/untaint signed numeric data (whole numbers).  If there is an error,
2020 returns the error, otherwise returns false.
2021
2022 =cut
2023
2024 sub ut_snumbern {
2025   my($self, $field) = @_;
2026   $self->getfield($field) =~ /^\s*(-?)\s*(\d*)\s*$/
2027     or return "Illegal (numeric) $field: ". $self->getfield($field);
2028   if ($1) {
2029     return "Illegal (numeric) $field: ". $self->getfield($field)
2030       unless $2;
2031   }
2032   $self->setfield($field, "$1$2");
2033   '';
2034 }
2035
2036 =item ut_number COLUMN
2037
2038 Check/untaint simple numeric data (whole numbers).  May not be null.  If there
2039 is an error, returns the error, otherwise returns false.
2040
2041 =cut
2042
2043 sub ut_number {
2044   my($self,$field)=@_;
2045   $self->getfield($field) =~ /^\s*(\d+)\s*$/
2046     or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
2047   $self->setfield($field,$1);
2048   '';
2049 }
2050
2051 =item ut_numbern COLUMN
2052
2053 Check/untaint simple numeric data (whole numbers).  May be null.  If there is
2054 an error, returns the error, otherwise returns false.
2055
2056 =cut
2057
2058 sub ut_numbern {
2059   my($self,$field)=@_;
2060   $self->getfield($field) =~ /^\s*(\d*)\s*$/
2061     or return "Illegal (numeric) $field: ". $self->getfield($field);
2062   $self->setfield($field,$1);
2063   '';
2064 }
2065
2066 =item ut_money COLUMN
2067
2068 Check/untaint monetary numbers.  May be negative.  Set to 0 if null.  If there
2069 is an error, returns the error, otherwise returns false.
2070
2071 =cut
2072
2073 sub ut_money {
2074   my($self,$field)=@_;
2075   $self->setfield($field, 0) if $self->getfield($field) eq '';
2076   $self->getfield($field) =~ /^\s*(\-)?\s*(\d*)(\.\d{2})?\s*$/
2077     or return "Illegal (money) $field: ". $self->getfield($field);
2078   #$self->setfield($field, "$1$2$3" || 0);
2079   $self->setfield($field, ( ($1||''). ($2||''). ($3||'') ) || 0);
2080   '';
2081 }
2082
2083 =item ut_moneyn COLUMN
2084
2085 Check/untaint monetary numbers.  May be negative.  If there
2086 is an error, returns the error, otherwise returns false.
2087
2088 =cut
2089
2090 sub ut_moneyn {
2091   my($self,$field)=@_;
2092   if ($self->getfield($field) eq '') {
2093     $self->setfield($field, '');
2094     return '';
2095   }
2096   $self->ut_money($field);
2097 }
2098
2099 =item ut_text COLUMN
2100
2101 Check/untaint text.  Alphanumerics, spaces, and the following punctuation
2102 symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < >
2103 May not be null.  If there is an error, returns the error, otherwise returns
2104 false.
2105
2106 =cut
2107
2108 sub ut_text {
2109   my($self,$field)=@_;
2110   #warn "msgcat ". \&msgcat. "\n";
2111   #warn "notexist ". \&notexist. "\n";
2112   #warn "AUTOLOAD ". \&AUTOLOAD. "\n";
2113   $self->getfield($field)
2114     =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>]+)$/
2115       or return gettext('illegal_or_empty_text'). " $field: ".
2116                  $self->getfield($field);
2117   $self->setfield($field,$1);
2118   '';
2119 }
2120
2121 =item ut_textn COLUMN
2122
2123 Check/untaint text.  Alphanumerics, spaces, and the following punctuation
2124 symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? /
2125 May be null.  If there is an error, returns the error, otherwise returns false.
2126
2127 =cut
2128
2129 sub ut_textn {
2130   my($self,$field)=@_;
2131   $self->getfield($field)
2132     =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
2133       or return gettext('illegal_text'). " $field: ". $self->getfield($field);
2134   $self->setfield($field,$1);
2135   '';
2136 }
2137
2138 =item ut_alpha COLUMN
2139
2140 Check/untaint alphanumeric strings (no spaces).  May not be null.  If there is
2141 an error, returns the error, otherwise returns false.
2142
2143 =cut
2144
2145 sub ut_alpha {
2146   my($self,$field)=@_;
2147   $self->getfield($field) =~ /^(\w+)$/
2148     or return "Illegal or empty (alphanumeric) $field: ".
2149               $self->getfield($field);
2150   $self->setfield($field,$1);
2151   '';
2152 }
2153
2154 =item ut_alphan COLUMN
2155
2156 Check/untaint alphanumeric strings (no spaces).  May be null.  If there is an
2157 error, returns the error, otherwise returns false.
2158
2159 =cut
2160
2161 sub ut_alphan {
2162   my($self,$field)=@_;
2163   $self->getfield($field) =~ /^(\w*)$/ 
2164     or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
2165   $self->setfield($field,$1);
2166   '';
2167 }
2168
2169 =item ut_alphasn COLUMN
2170
2171 Check/untaint alphanumeric strings, spaces allowed.  May be null.  If there is
2172 an error, returns the error, otherwise returns false.
2173
2174 =cut
2175
2176 sub ut_alphasn {
2177   my($self,$field)=@_;
2178   $self->getfield($field) =~ /^([\w ]*)$/ 
2179     or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
2180   $self->setfield($field,$1);
2181   '';
2182 }
2183
2184
2185 =item ut_alpha_lower COLUMN
2186
2187 Check/untaint lowercase alphanumeric strings (no spaces).  May not be null.  If
2188 there is an error, returns the error, otherwise returns false.
2189
2190 =cut
2191
2192 sub ut_alpha_lower {
2193   my($self,$field)=@_;
2194   $self->getfield($field) =~ /[[:upper:]]/
2195     and return "Uppercase characters are not permitted in $field";
2196   $self->ut_alpha($field);
2197 }
2198
2199 =item ut_phonen COLUMN [ COUNTRY ]
2200
2201 Check/untaint phone numbers.  May be null.  If there is an error, returns
2202 the error, otherwise returns false.
2203
2204 Takes an optional two-letter ISO country code; without it or with unsupported
2205 countries, ut_phonen simply calls ut_alphan.
2206
2207 =cut
2208
2209 sub ut_phonen {
2210   my( $self, $field, $country ) = @_;
2211   return $self->ut_alphan($field) unless defined $country;
2212   my $phonen = $self->getfield($field);
2213   if ( $phonen eq '' ) {
2214     $self->setfield($field,'');
2215   } elsif ( $country eq 'US' || $country eq 'CA' ) {
2216     $phonen =~ s/\D//g;
2217     $phonen = $conf->config('cust_main-default_areacode').$phonen
2218       if length($phonen)==7 && $conf->config('cust_main-default_areacode');
2219     $phonen =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/
2220       or return gettext('illegal_phone'). " $field: ". $self->getfield($field);
2221     $phonen = "$1-$2-$3";
2222     $phonen .= " x$4" if $4;
2223     $self->setfield($field,$phonen);
2224   } else {
2225     warn "warning: don't know how to check phone numbers for country $country";
2226     return $self->ut_textn($field);
2227   }
2228   '';
2229 }
2230
2231 =item ut_hex COLUMN
2232
2233 Check/untaint hexadecimal values.
2234
2235 =cut
2236
2237 sub ut_hex {
2238   my($self, $field) = @_;
2239   $self->getfield($field) =~ /^([\da-fA-F]+)$/
2240     or return "Illegal (hex) $field: ". $self->getfield($field);
2241   $self->setfield($field, uc($1));
2242   '';
2243 }
2244
2245 =item ut_hexn COLUMN
2246
2247 Check/untaint hexadecimal values.  May be null.
2248
2249 =cut
2250
2251 sub ut_hexn {
2252   my($self, $field) = @_;
2253   $self->getfield($field) =~ /^([\da-fA-F]*)$/
2254     or return "Illegal (hex) $field: ". $self->getfield($field);
2255   $self->setfield($field, uc($1));
2256   '';
2257 }
2258 =item ut_ip COLUMN
2259
2260 Check/untaint ip addresses.  IPv4 only for now, though ::1 is auto-translated
2261 to 127.0.0.1.
2262
2263 =cut
2264
2265 sub ut_ip {
2266   my( $self, $field ) = @_;
2267   $self->setfield($field, '127.0.0.1') if $self->getfield($field) eq '::1';
2268   $self->getfield($field) =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
2269     or return "Illegal (IP address) $field: ". $self->getfield($field);
2270   for ( $1, $2, $3, $4 ) { return "Illegal (IP address) $field" if $_ > 255; }
2271   $self->setfield($field, "$1.$2.$3.$4");
2272   '';
2273 }
2274
2275 =item ut_ipn COLUMN
2276
2277 Check/untaint ip addresses.  IPv4 only for now, though ::1 is auto-translated
2278 to 127.0.0.1.  May be null.
2279
2280 =cut
2281
2282 sub ut_ipn {
2283   my( $self, $field ) = @_;
2284   if ( $self->getfield($field) =~ /^()$/ ) {
2285     $self->setfield($field,'');
2286     '';
2287   } else {
2288     $self->ut_ip($field);
2289   }
2290 }
2291
2292 =item ut_coord COLUMN [ LOWER [ UPPER ] ]
2293
2294 Check/untaint coordinates.
2295 Accepts the following forms:
2296 DDD.DDDDD
2297 -DDD.DDDDD
2298 DDD MM.MMM
2299 -DDD MM.MMM
2300 DDD MM SS
2301 -DDD MM SS
2302 DDD MM MMM
2303 -DDD MM MMM
2304
2305 The "DDD MM SS" and "DDD MM MMM" are potentially ambiguous.
2306 The latter form (that is, the MMM are thousands of minutes) is
2307 assumed if the "MMM" is exactly three digits or two digits > 59.
2308
2309 To be safe, just use the DDD.DDDDD form.
2310
2311 If LOWER or UPPER are specified, then the coordinate is checked
2312 for lower and upper bounds, respectively.
2313
2314 =cut
2315
2316 sub ut_coord {
2317
2318   my ($self, $field) = (shift, shift);
2319
2320   my $lower = shift if scalar(@_);
2321   my $upper = shift if scalar(@_);
2322   my $coord = $self->getfield($field);
2323   my $neg = $coord =~ s/^(-)//;
2324
2325   my ($d, $m, $s) = (0, 0, 0);
2326
2327   if (
2328     (($d) = ($coord =~ /^(\s*\d{1,3}(?:\.\d+)?)\s*$/)) ||
2329     (($d, $m) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2}(?:\.\d+))\s*$/)) ||
2330     (($d, $m, $s) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2})\s+(\d{1,3})\s*$/))
2331   ) {
2332     $s = (((($s =~ /^\d{3}$/) or $s > 59) ? ($s / 1000) : ($s / 60)) / 60);
2333     $m = $m / 60;
2334     if ($m > 59) {
2335       return "Invalid (coordinate with minutes > 59) $field: "
2336              . $self->getfield($field);
2337     }
2338
2339     $coord = ($neg ? -1 : 1) * sprintf('%.8f', $d + $m + $s);
2340
2341     if (defined($lower) and ($coord < $lower)) {
2342       return "Invalid (coordinate < $lower) $field: "
2343              . $self->getfield($field);;
2344     }
2345
2346     if (defined($upper) and ($coord > $upper)) {
2347       return "Invalid (coordinate > $upper) $field: "
2348              . $self->getfield($field);;
2349     }
2350
2351     $self->setfield($field, $coord);
2352     return '';
2353   }
2354
2355   return "Invalid (coordinate) $field: " . $self->getfield($field);
2356
2357 }
2358
2359 =item ut_coordn COLUMN [ LOWER [ UPPER ] ]
2360
2361 Same as ut_coord, except optionally null.
2362
2363 =cut
2364
2365 sub ut_coordn {
2366
2367   my ($self, $field) = (shift, shift);
2368
2369   if ($self->getfield($field) =~ /^$/) {
2370     return '';
2371   } else {
2372     return $self->ut_coord($field, @_);
2373   }
2374
2375 }
2376
2377
2378 =item ut_domain COLUMN
2379
2380 Check/untaint host and domain names.
2381
2382 =cut
2383
2384 sub ut_domain {
2385   my( $self, $field ) = @_;
2386   #$self->getfield($field) =~/^(\w+\.)*\w+$/
2387   $self->getfield($field) =~/^(([\w\-]+\.)*\w+)$/
2388     or return "Illegal (domain) $field: ". $self->getfield($field);
2389   $self->setfield($field,$1);
2390   '';
2391 }
2392
2393 =item ut_name COLUMN
2394
2395 Check/untaint proper names; allows alphanumerics, spaces and the following
2396 punctuation: , . - '
2397
2398 May not be null.
2399
2400 =cut
2401
2402 sub ut_name {
2403   my( $self, $field ) = @_;
2404   $self->getfield($field) =~ /^([\w \,\.\-\']+)$/
2405     or return gettext('illegal_name'). " $field: ". $self->getfield($field);
2406   $self->setfield($field,$1);
2407   '';
2408 }
2409
2410 =item ut_zip COLUMN
2411
2412 Check/untaint zip codes.
2413
2414 =cut
2415
2416 my @zip_reqd_countries = qw( AU CA US ); #CA, US implicit...
2417
2418 sub ut_zip {
2419   my( $self, $field, $country ) = @_;
2420
2421   if ( $country eq 'US' ) {
2422
2423     $self->getfield($field) =~ /^\s*(\d{5}(\-\d{4})?)\s*$/
2424       or return gettext('illegal_zip'). " $field for country $country: ".
2425                 $self->getfield($field);
2426     $self->setfield($field, $1);
2427
2428   } elsif ( $country eq 'CA' ) {
2429
2430     $self->getfield($field) =~ /^\s*([A-Z]\d[A-Z])\s*(\d[A-Z]\d)\s*$/i
2431       or return gettext('illegal_zip'). " $field for country $country: ".
2432                 $self->getfield($field);
2433     $self->setfield($field, "$1 $2");
2434
2435   } else {
2436
2437     if ( $self->getfield($field) =~ /^\s*$/
2438          && ( !$country || ! grep { $_ eq $country } @zip_reqd_countries )
2439        )
2440     {
2441       $self->setfield($field,'');
2442     } else {
2443       $self->getfield($field) =~ /^\s*(\w[\w\-\s]{2,8}\w)\s*$/
2444         or return gettext('illegal_zip'). " $field: ". $self->getfield($field);
2445       $self->setfield($field,$1);
2446     }
2447
2448   }
2449
2450   '';
2451 }
2452
2453 =item ut_country COLUMN
2454
2455 Check/untaint country codes.  Country names are changed to codes, if possible -
2456 see L<Locale::Country>.
2457
2458 =cut
2459
2460 sub ut_country {
2461   my( $self, $field ) = @_;
2462   unless ( $self->getfield($field) =~ /^(\w\w)$/ ) {
2463     if ( $self->getfield($field) =~ /^([\w \,\.\(\)\']+)$/ 
2464          && country2code($1) ) {
2465       $self->setfield($field,uc(country2code($1)));
2466     }
2467   }
2468   $self->getfield($field) =~ /^(\w\w)$/
2469     or return "Illegal (country) $field: ". $self->getfield($field);
2470   $self->setfield($field,uc($1));
2471   '';
2472 }
2473
2474 =item ut_anything COLUMN
2475
2476 Untaints arbitrary data.  Be careful.
2477
2478 =cut
2479
2480 sub ut_anything {
2481   my( $self, $field ) = @_;
2482   $self->getfield($field) =~ /^(.*)$/s
2483     or return "Illegal $field: ". $self->getfield($field);
2484   $self->setfield($field,$1);
2485   '';
2486 }
2487
2488 =item ut_enum COLUMN CHOICES_ARRAYREF
2489
2490 Check/untaint a column, supplying all possible choices, like the "enum" type.
2491
2492 =cut
2493
2494 sub ut_enum {
2495   my( $self, $field, $choices ) = @_;
2496   foreach my $choice ( @$choices ) {
2497     if ( $self->getfield($field) eq $choice ) {
2498       $self->setfield($field, $choice);
2499       return '';
2500     }
2501   }
2502   return "Illegal (enum) field $field: ". $self->getfield($field);
2503 }
2504
2505 =item ut_foreign_key COLUMN FOREIGN_TABLE FOREIGN_COLUMN
2506
2507 Check/untaint a foreign column key.  Call a regular ut_ method (like ut_number)
2508 on the column first.
2509
2510 =cut
2511
2512 sub ut_foreign_key {
2513   my( $self, $field, $table, $foreign ) = @_;
2514   return '' if $no_check_foreign;
2515   qsearchs($table, { $foreign => $self->getfield($field) })
2516     or return "Can't find ". $self->table. ".$field ". $self->getfield($field).
2517               " in $table.$foreign";
2518   '';
2519 }
2520
2521 =item ut_foreign_keyn COLUMN FOREIGN_TABLE FOREIGN_COLUMN
2522
2523 Like ut_foreign_key, except the null value is also allowed.
2524
2525 =cut
2526
2527 sub ut_foreign_keyn {
2528   my( $self, $field, $table, $foreign ) = @_;
2529   $self->getfield($field)
2530     ? $self->ut_foreign_key($field, $table, $foreign)
2531     : '';
2532 }
2533
2534 =item ut_agentnum_acl COLUMN [ NULL_RIGHT | NULL_RIGHT_LISTREF ]
2535
2536 Checks this column as an agentnum, taking into account the current users's
2537 ACLs.  NULL_RIGHT or NULL_RIGHT_LISTREF, if specified, indicates the access
2538 right or rights allowing no agentnum.
2539
2540 =cut
2541
2542 sub ut_agentnum_acl {
2543   my( $self, $field ) = (shift, shift);
2544   my $null_acl = scalar(@_) ? shift : [];
2545   $null_acl = [ $null_acl ] unless ref($null_acl);
2546
2547   my $error = $self->ut_foreign_keyn($field, 'agent', 'agentnum');
2548   return "Illegal agentnum: $error" if $error;
2549
2550   my $curuser = $FS::CurrentUser::CurrentUser;
2551
2552   if ( $self->$field() ) {
2553
2554     return "Access denied"
2555       unless $curuser->agentnum($self->$field());
2556
2557   } else {
2558
2559     return "Access denied"
2560       unless grep $curuser->access_right($_), @$null_acl;
2561
2562   }
2563
2564   '';
2565
2566 }
2567
2568 =item virtual_fields [ TABLE ]
2569
2570 Returns a list of virtual fields defined for the table.  This should not 
2571 be exported, and should only be called as an instance or class method.
2572
2573 =cut
2574
2575 sub virtual_fields {
2576   my $self = shift;
2577   my $table;
2578   $table = $self->table or confess "virtual_fields called on non-table";
2579
2580   confess "Unknown table $table" unless dbdef->table($table);
2581
2582   return () unless dbdef->table('part_virtual_field');
2583
2584   unless ( $virtual_fields_cache{$table} ) {
2585     my $query = 'SELECT name from part_virtual_field ' .
2586                 "WHERE dbtable = '$table'";
2587     my $dbh = dbh;
2588     my $result = $dbh->selectcol_arrayref($query);
2589     confess "Error executing virtual fields query: $query: ". $dbh->errstr
2590       if $dbh->err;
2591     $virtual_fields_cache{$table} = $result;
2592   }
2593
2594   @{$virtual_fields_cache{$table}};
2595
2596 }
2597
2598
2599 =item fields [ TABLE ]
2600
2601 This is a wrapper for real_fields and virtual_fields.  Code that called
2602 fields before should probably continue to call fields.
2603
2604 =cut
2605
2606 sub fields {
2607   my $something = shift;
2608   my $table;
2609   if($something->isa('FS::Record')) {
2610     $table = $something->table;
2611   } else {
2612     $table = $something;
2613     $something = "FS::$table";
2614   }
2615   return (real_fields($table), $something->virtual_fields());
2616 }
2617
2618 =item pvf FIELD_NAME
2619
2620 Returns the FS::part_virtual_field object corresponding to a field in the 
2621 record (specified by FIELD_NAME).
2622
2623 =cut
2624
2625 sub pvf {
2626   my ($self, $name) = (shift, shift);
2627
2628   if(grep /^$name$/, $self->virtual_fields) {
2629     return qsearchs('part_virtual_field', { dbtable => $self->table,
2630                                             name    => $name } );
2631   }
2632   ''
2633 }
2634
2635 =item vfieldpart_hashref TABLE
2636
2637 Returns a hashref of virtual field names and vfieldparts applicable to the given
2638 TABLE.
2639
2640 =cut
2641
2642 sub vfieldpart_hashref {
2643   my $self = shift;
2644   my $table = $self->table;
2645
2646   return {} unless dbdef->table('part_virtual_field');
2647
2648   my $dbh = dbh;
2649   my $statement = "SELECT vfieldpart, name FROM part_virtual_field WHERE ".
2650                   "dbtable = '$table'";
2651   my $sth = $dbh->prepare($statement);
2652   $sth->execute or croak "Execution of '$statement' failed: ".$dbh->errstr;
2653   return { map { $_->{name}, $_->{vfieldpart} } 
2654     @{$sth->fetchall_arrayref({})} };
2655
2656 }
2657
2658 =item encrypt($value)
2659
2660 Encrypts the credit card using a combination of PK to encrypt and uuencode to armour.
2661
2662 Returns the encrypted string.
2663
2664 You should generally not have to worry about calling this, as the system handles this for you.
2665
2666 =cut
2667
2668 sub encrypt {
2669   my ($self, $value) = @_;
2670   my $encrypted;
2671
2672   if ($conf->exists('encryption')) {
2673     if ($self->is_encrypted($value)) {
2674       # Return the original value if it isn't plaintext.
2675       $encrypted = $value;
2676     } else {
2677       $self->loadRSA;
2678       if (ref($rsa_encrypt) =~ /::RSA/) { # We Can Encrypt
2679         # RSA doesn't like the empty string so let's pack it up
2680         # The database doesn't like the RSA data so uuencode it
2681         my $length = length($value)+1;
2682         $encrypted = pack("u*",$rsa_encrypt->encrypt(pack("Z$length",$value)));
2683       } else {
2684         die ("You can't encrypt w/o a valid RSA engine - Check your installation or disable encryption");
2685       }
2686     }
2687   }
2688   return $encrypted;
2689 }
2690
2691 =item is_encrypted($value)
2692
2693 Checks to see if the string is encrypted and returns true or false (1/0) to indicate it's status.
2694
2695 =cut
2696
2697
2698 sub is_encrypted {
2699   my ($self, $value) = @_;
2700   # Possible Bug - Some work may be required here....
2701
2702   if ($value =~ /^M/ && length($value) > 80) {
2703     return 1;
2704   } else {
2705     return 0;
2706   }
2707 }
2708
2709 =item decrypt($value)
2710
2711 Uses the private key to decrypt the string. Returns the decryoted string or undef on failure.
2712
2713 You should generally not have to worry about calling this, as the system handles this for you.
2714
2715 =cut
2716
2717 sub decrypt {
2718   my ($self,$value) = @_;
2719   my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
2720   if ($conf->exists('encryption') && $self->is_encrypted($value)) {
2721     $self->loadRSA;
2722     if (ref($rsa_decrypt) =~ /::RSA/) {
2723       my $encrypted = unpack ("u*", $value);
2724       $decrypted =  unpack("Z*", eval{$rsa_decrypt->decrypt($encrypted)});
2725       if ($@) {warn "Decryption Failed"};
2726     }
2727   }
2728   return $decrypted;
2729 }
2730
2731 sub loadRSA {
2732     my $self = shift;
2733     #Initialize the Module
2734     $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default
2735
2736     if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') {
2737       $rsa_module = $conf->config('encryptionmodule');
2738     }
2739
2740     if (!$rsa_loaded) {
2741         eval ("require $rsa_module"); # No need to import the namespace
2742         $rsa_loaded++;
2743     }
2744     # Initialize Encryption
2745     if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') {
2746       my $public_key = join("\n",$conf->config('encryptionpublickey'));
2747       $rsa_encrypt = $rsa_module->new_public_key($public_key);
2748     }
2749     
2750     # Intitalize Decryption
2751     if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') {
2752       my $private_key = join("\n",$conf->config('encryptionprivatekey'));
2753       $rsa_decrypt = $rsa_module->new_private_key($private_key);
2754     }
2755 }
2756
2757 =item h_search ACTION
2758
2759 Given an ACTION, either "insert", or "delete", returns the appropriate history
2760 record corresponding to this record, if any.
2761
2762 =cut
2763
2764 sub h_search {
2765   my( $self, $action ) = @_;
2766
2767   my $table = $self->table;
2768   $table =~ s/^h_//;
2769
2770   my $primary_key = dbdef->table($table)->primary_key;
2771
2772   qsearchs({
2773     'table'   => "h_$table",
2774     'hashref' => { $primary_key     => $self->$primary_key(),
2775                    'history_action' => $action,
2776                  },
2777   });
2778
2779 }
2780
2781 =item h_date ACTION
2782
2783 Given an ACTION, either "insert", or "delete", returns the timestamp of the
2784 appropriate history record corresponding to this record, if any.
2785
2786 =cut
2787
2788 sub h_date {
2789   my($self, $action) = @_;
2790   my $h = $self->h_search($action);
2791   $h ? $h->history_date : '';
2792 }
2793
2794 =item scalar_sql SQL
2795
2796 A class method with a propensity for becoming an instance method.  This
2797 method executes the sql statement represented by SQL and returns a scalar
2798 representing the result.  Don't ask for rows -- you get the first column
2799 of the first row.  Don't give me bogus SQL or I'll die on you.
2800
2801 Returns an empty string in the event of no rows.
2802
2803 =cut
2804
2805 sub scalar_sql {
2806   my($self, $sql ) = ( shift, shift );
2807   my $sth = dbh->prepare($sql) or die dbh->errstr;
2808   $sth->execute
2809     or die "Unexpected error executing statement $sql: ". $sth->errstr;
2810   my $scalar = $sth->fetchrow_arrayref->[0];
2811   defined($scalar) ? $scalar : '';
2812 }
2813
2814 =back
2815
2816 =head1 SUBROUTINES
2817
2818 =over 4
2819
2820 =item real_fields [ TABLE ]
2821
2822 Returns a list of the real columns in the specified table.  Called only by 
2823 fields() and other subroutines elsewhere in FS::Record.
2824
2825 =cut
2826
2827 sub real_fields {
2828   my $table = shift;
2829
2830   my($table_obj) = dbdef->table($table);
2831   confess "Unknown table $table" unless $table_obj;
2832   $table_obj->columns;
2833 }
2834
2835 =item _quote VALUE, TABLE, COLUMN
2836
2837 This is an internal function used to construct SQL statements.  It returns
2838 VALUE DBI-quoted (see L<DBI/"quote">) unless VALUE is a number and the column
2839 type (see L<DBIx::DBSchema::Column>) does not end in `char' or `binary'.
2840
2841 =cut
2842
2843 sub _quote {
2844   my($value, $table, $column) = @_;
2845   my $column_obj = dbdef->table($table)->column($column);
2846   my $column_type = $column_obj->type;
2847   my $nullable = $column_obj->null;
2848
2849   warn "  $table.$column: $value ($column_type".
2850        ( $nullable ? ' NULL' : ' NOT NULL' ).
2851        ")\n" if $DEBUG > 2;
2852
2853   if ( $value eq '' && $nullable ) {
2854     'NULL';
2855   } elsif ( $value eq '' && $column_type =~ /^(int|numeric)/ ) {
2856     cluck "WARNING: Attempting to set non-null integer $table.$column null; ".
2857           "using 0 instead";
2858     0;
2859   } elsif ( $value =~ /^\d+(\.\d+)?$/ && 
2860             ! $column_type =~ /(char|binary|text)$/i ) {
2861     $value;
2862   } elsif (( $column_type =~ /^bytea$/i || $column_type =~ /(blob|varbinary)/i )
2863            && driver_name eq 'Pg'
2864           )
2865   {
2866     no strict 'subs';
2867 #    dbh->quote($value, { pg_type => PG_BYTEA() }); # doesn't work right
2868     # Pg binary string quoting: convert each character to 3-digit octal prefixed with \\, 
2869     # single-quote the whole mess, and put an "E" in front.
2870     return ("E'" . join('', map { sprintf('\\\\%03o', ord($_)) } split(//, $value) ) . "'");
2871   } else {
2872     dbh->quote($value);
2873   }
2874 }
2875
2876 =item hfields TABLE
2877
2878 This is deprecated.  Don't use it.
2879
2880 It returns a hash-type list with the fields of this record's table set true.
2881
2882 =cut
2883
2884 sub hfields {
2885   carp "warning: hfields is deprecated";
2886   my($table)=@_;
2887   my(%hash);
2888   foreach (fields($table)) {
2889     $hash{$_}=1;
2890   }
2891   \%hash;
2892 }
2893
2894 sub _dump {
2895   my($self)=@_;
2896   join("\n", map {
2897     "$_: ". $self->getfield($_). "|"
2898   } (fields($self->table)) );
2899 }
2900
2901 sub DESTROY { return; }
2902
2903 #sub DESTROY {
2904 #  my $self = shift;
2905 #  #use Carp qw(cluck);
2906 #  #cluck "DESTROYING $self";
2907 #  warn "DESTROYING $self";
2908 #}
2909
2910 #sub is_tainted {
2911 #             return ! eval { join('',@_), kill 0; 1; };
2912 #         }
2913
2914 =item str2time_sql [ DRIVER_NAME ]
2915
2916 Returns a function to convert to unix time based on database type, such as
2917 "EXTRACT( EPOCH FROM" for Pg or "UNIX_TIMESTAMP(" for mysql.  See
2918 the str2time_sql_closing method to return a closing string rather than just
2919 using a closing parenthesis as previously suggested.
2920
2921 You can pass an optional driver name such as "Pg", "mysql" or
2922 $dbh->{Driver}->{Name} to return a function for that database instead of
2923 the current database.
2924
2925 =cut
2926
2927 sub str2time_sql { 
2928   my $driver = shift || driver_name;
2929
2930   return 'UNIX_TIMESTAMP('      if $driver =~ /^mysql/i;
2931   return 'EXTRACT( EPOCH FROM ' if $driver =~ /^Pg/i;
2932
2933   warn "warning: unknown database type $driver; guessing how to convert ".
2934        "dates to UNIX timestamps";
2935   return 'EXTRACT(EPOCH FROM ';
2936
2937 }
2938
2939 =item str2time_sql_closing [ DRIVER_NAME ]
2940
2941 Returns the closing suffix of a function to convert to unix time based on
2942 database type, such as ")::integer" for Pg or ")" for mysql.
2943
2944 You can pass an optional driver name such as "Pg", "mysql" or
2945 $dbh->{Driver}->{Name} to return a function for that database instead of
2946 the current database.
2947
2948 =cut
2949
2950 sub str2time_sql_closing { 
2951   my $driver = shift || driver_name;
2952
2953   return ' )::INTEGER ' if $driver =~ /^Pg/i;
2954   return ' ) ';
2955 }
2956
2957 =back
2958
2959 =head1 BUGS
2960
2961 This module should probably be renamed, since much of the functionality is
2962 of general use.  It is not completely unlike Adapter::DBI (see below).
2963
2964 Exported qsearch and qsearchs should be deprecated in favor of method calls
2965 (against an FS::Record object like the old search and searchs that qsearch
2966 and qsearchs were on top of.)
2967
2968 The whole fields / hfields mess should be removed.
2969
2970 The various WHERE clauses should be subroutined.
2971
2972 table string should be deprecated in favor of DBIx::DBSchema::Table.
2973
2974 No doubt we could benefit from a Tied hash.  Documenting how exists / defined
2975 true maps to the database (and WHERE clauses) would also help.
2976
2977 The ut_ methods should ask the dbdef for a default length.
2978
2979 ut_sqltype (like ut_varchar) should all be defined
2980
2981 A fallback check method should be provided which uses the dbdef.
2982
2983 The ut_money method assumes money has two decimal digits.
2984
2985 The Pg money kludge in the new method only strips `$'.
2986
2987 The ut_phonen method only checks US-style phone numbers.
2988
2989 The _quote function should probably use ut_float instead of a regex.
2990
2991 All the subroutines probably should be methods, here or elsewhere.
2992
2993 Probably should borrow/use some dbdef methods where appropriate (like sub
2994 fields)
2995
2996 As of 1.14, DBI fetchall_hashref( {} ) doesn't set fetchrow_hashref NAME_lc,
2997 or allow it to be set.  Working around it is ugly any way around - DBI should
2998 be fixed.  (only affects RDBMS which return uppercase column names)
2999
3000 ut_zip should take an optional country like ut_phone.
3001
3002 =head1 SEE ALSO
3003
3004 L<DBIx::DBSchema>, L<FS::UID>, L<DBI>
3005
3006 Adapter::DBI from Ch. 11 of Advanced Perl Programming by Sriram Srinivasan.
3007
3008 http://poop.sf.net/
3009
3010 =cut
3011
3012 1;
3013