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