top bar option!
[freeside.git] / FS / FS / option_Common.pm
1 package FS::option_Common;
2
3 use strict;
4 use vars qw( @ISA $DEBUG );
5 use FS::Record qw( qsearch qsearchs dbh );
6
7 @ISA = qw( FS::Record );
8
9 $DEBUG = 3;
10
11 =head1 NAME
12
13 FS::option_Common - Base class for option sub-classes
14
15 =head1 SYNOPSIS
16
17 use FS::option_Common;
18
19 @ISA = qw( FS::option_Common );
20
21 #optional for non-standard names
22 sub _option_table    { 'table_name'; }  #defaults to ${table}_option
23 sub _option_namecol  { 'column_name'; } #defaults to optionname
24 sub _option_valuecol { 'column_name'; } #defaults to optionvalue
25
26 =head1 DESCRIPTION
27
28 FS::option_Common is intended as a base class for classes which have a
29 simple one-to-many class associated with them, used to store a hash-like data
30 structure of keys and values.
31
32 =head1 METHODS
33
34 =over 4
35
36 =item insert [ HASHREF | OPTION => VALUE ... ]
37
38 Adds this record to the database.  If there is an error, returns the error,
39 otherwise returns false.
40
41 If a list or hash reference of options is supplied, option records are also
42 created.
43
44 =cut
45
46 #false laziness w/queue.pm
47 sub insert {
48   my $self = shift;
49   my $options = 
50     ( ref($_[0]) eq 'HASH' )
51       ? shift
52       : { @_ };
53   warn "FS::option_Common::insert called on $self with options ".
54        join(', ', map "$_ => ".$options->{$_}, keys %$options)
55     if $DEBUG;
56
57   local $SIG{HUP} = 'IGNORE';
58   local $SIG{INT} = 'IGNORE';
59   local $SIG{QUIT} = 'IGNORE';
60   local $SIG{TERM} = 'IGNORE';
61   local $SIG{TSTP} = 'IGNORE';
62   local $SIG{PIPE} = 'IGNORE';
63
64   my $oldAutoCommit = $FS::UID::AutoCommit;
65   local $FS::UID::AutoCommit = 0;
66   my $dbh = dbh;
67
68   my $error = $self->SUPER::insert;
69   if ( $error ) {
70     $dbh->rollback if $oldAutoCommit;
71     return $error;
72   }
73
74   my $pkey = $self->primary_key;
75   my $option_table = $self->option_table;
76
77   my $namecol = $self->_option_namecol;
78   my $valuecol = $self->_option_valuecol;
79
80   foreach my $optionname ( keys %{$options} ) {
81     my $href = {
82       $pkey     => $self->get($pkey),
83       $namecol  => $optionname,
84       $valuecol => $options->{$optionname},
85     };
86
87     #my $option_record = eval "new FS::$option_table \$href";
88     #if ( $@ ) {
89     #  $dbh->rollback if $oldAutoCommit;
90     #  return $@;
91     #}
92     my $option_record = "FS::$option_table"->new($href);
93
94     $error = $option_record->insert;
95     if ( $error ) {
96       $dbh->rollback if $oldAutoCommit;
97       return $error;
98     }
99   }
100
101   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
102
103   '';
104
105 }
106
107 =item delete
108
109 Delete this record from the database.  Any associated option records are also
110 deleted.
111
112 =cut
113
114 #foreign keys would make this much less tedious... grr dumb mysql
115 sub delete {
116   my $self = shift;
117   local $SIG{HUP} = 'IGNORE';
118   local $SIG{INT} = 'IGNORE';
119   local $SIG{QUIT} = 'IGNORE';
120   local $SIG{TERM} = 'IGNORE';
121   local $SIG{TSTP} = 'IGNORE';
122   local $SIG{PIPE} = 'IGNORE';
123
124   my $oldAutoCommit = $FS::UID::AutoCommit;
125   local $FS::UID::AutoCommit = 0;
126   my $dbh = dbh;
127
128   my $error = $self->SUPER::delete;
129   if ( $error ) {
130     $dbh->rollback if $oldAutoCommit;
131     return $error;
132   }
133   
134   my $pkey = $self->primary_key;
135   #my $option_table = $self->option_table;
136
137   foreach my $obj ( $self->option_objects ) {
138     my $error = $obj->delete;
139     if ( $error ) {
140       $dbh->rollback if $oldAutoCommit;
141       return $error;
142     }
143   }
144
145   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
146
147   '';
148
149 }
150
151 =item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ... ]
152
153 Replaces the OLD_RECORD with this one in the database.  If there is an error,
154 returns the error, otherwise returns false.
155
156 If a list hash reference of options is supplied, part_export_option records are
157 created or modified (see L<FS::part_export_option>).
158
159 =cut
160
161 sub replace {
162   my $self = shift;
163
164   my $old = ( ref($_[0]) eq ref($self) )
165               ? shift
166               : $self->replace_old;
167
168   my $options = 
169     ( ref($_[0]) eq 'HASH' )
170       ? shift
171       : { @_ };
172   warn "FS::option_Common::replace called on $self with options ".
173        join(', ', map "$_ => ". $options->{$_}, keys %$options)
174     if $DEBUG;
175
176   local $SIG{HUP} = 'IGNORE';
177   local $SIG{INT} = 'IGNORE';
178   local $SIG{QUIT} = 'IGNORE';
179   local $SIG{TERM} = 'IGNORE';
180   local $SIG{TSTP} = 'IGNORE';
181   local $SIG{PIPE} = 'IGNORE';
182
183   my $oldAutoCommit = $FS::UID::AutoCommit;
184   local $FS::UID::AutoCommit = 0;
185   my $dbh = dbh;
186
187   my $error = $self->SUPER::replace($old);
188   if ( $error ) {
189     $dbh->rollback if $oldAutoCommit;
190     return $error;
191   }
192
193   my $pkey = $self->primary_key;
194   my $option_table = $self->option_table;
195
196   my $namecol = $self->_option_namecol;
197   my $valuecol = $self->_option_valuecol;
198
199   foreach my $optionname ( keys %{$options} ) {
200
201     warn "FS::option_Common::replace: inserting or replacing option: $optionname"
202       if $DEBUG > 1;
203
204     my $oldopt = qsearchs( $option_table, {
205         $pkey    => $self->get($pkey),
206         $namecol => $optionname,
207     } );
208
209     my $href = {
210         $pkey     => $self->get($pkey),
211         $namecol  => $optionname,
212         $valuecol => $options->{$optionname},
213     };
214
215     #my $newopt = eval "new FS::$option_table \$href";
216     #if ( $@ ) {
217     #  $dbh->rollback if $oldAutoCommit;
218     #  return $@;
219     #}
220     my $newopt = "FS::$option_table"->new($href);
221
222     my $opt_pkey = $newopt->primary_key;
223
224     $newopt->$opt_pkey($oldopt->$opt_pkey) if $oldopt;
225     warn $oldopt;
226     warn "FS::option_Common::replace: ".
227          ( $oldopt ? "$newopt -> replace($oldopt)" : "$newopt -> insert" )
228       if $DEBUG > 2;
229     my $error = $oldopt ? $newopt->replace($oldopt) : $newopt->insert;
230     warn $error;
231     if ( $error ) {
232       $dbh->rollback if $oldAutoCommit;
233       return $error;
234     }
235   }
236
237   #remove extraneous old options
238   foreach my $opt (
239     grep { !exists $options->{$_->$namecol()} } $old->option_objects
240   ) {
241     my $error = $opt->delete;
242     if ( $error ) {
243       $dbh->rollback if $oldAutoCommit;
244       return $error;
245     }
246   }
247
248   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
249
250   '';
251
252 }
253
254 =item option_objects
255
256 Returns all options as FS::I<tablename>_option objects.
257
258 =cut
259
260 sub option_objects {
261   my $self = shift;
262   my $pkey = $self->primary_key;
263   my $option_table = $self->option_table;
264   qsearch($option_table, { $pkey => $self->get($pkey) } );
265 }
266
267 =item options 
268
269 Returns a list of option names and values suitable for assigning to a hash.
270
271 =cut
272
273 sub options {
274   my $self = shift;
275   my $namecol = $self->_option_namecol;
276   my $valuecol = $self->_option_valuecol;
277   map { $_->$namecol() => $_->$valuecol() } $self->option_objects;
278 }
279
280 =item option OPTIONNAME
281
282 Returns the option value for the given name, or the empty string.
283
284 =cut
285
286 sub option {
287   my $self = shift;
288   my $pkey = $self->primary_key;
289   my $option_table = $self->option_table;
290   my $namecol = $self->_option_namecol;
291   my $valuecol = $self->_option_valuecol;
292   my $hashref = {
293       $pkey    => $self->get($pkey),
294       $namecol => shift,
295   };
296   warn "$self -> option: searching for ".
297          join(' / ', map { "$_ => ". $hashref->{$_} } keys %$hashref )
298     if $DEBUG;
299   my $obj = qsearchs($option_table, $hashref);
300   $obj ? $obj->$valuecol() : '';
301 }
302
303
304 sub option_table {
305   my $self = shift;
306   my $option_table = $self->_option_table;
307   eval "use FS::$option_table";
308   die $@ if $@;
309   $option_table;
310 }
311
312 #defaults
313 sub _option_table    { shift->table .'_option'; }
314 sub _option_namecol  { 'optionname'; }
315 sub _option_valuecol { 'optionvalue'; }
316
317 =back
318
319 =head1 BUGS
320
321 =head1 SEE ALSO
322
323 L<FS::Record>
324
325 =cut
326
327 1;
328