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