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