don't re-my var, quiet warning
[freeside.git] / FS / FS / part_virtual_field.pm
1 package FS::part_virtual_field;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::Record qw( qsearchs qsearch dbdef );
6
7 @ISA = qw( FS::Record );
8
9 =head1 NAME
10
11 FS::part_virtual_field - Object methods for part_virtual_field records
12
13 =head1 SYNOPSIS
14
15   use FS::part_virtual_field;
16
17   $record = new FS::part_virtual_field \%hash;
18   $record = new FS::part_virtual_field { 'column' => 'value' };
19
20   $error = $record->insert;
21
22   $error = $new_record->replace($old_record);
23
24   $error = $record->delete;
25
26   $error = $record->check;
27
28 =head1 DESCRIPTION
29
30 An FS::part_virtual_field object represents the definition of a virtual field 
31 (see the BACKGROUND section).  FS::part_virtual_field contains the name and 
32 base table of the field, as well as validation rules and UI hints about the 
33 display of the field.  The actual data is stored in FS::virtual_field; see 
34 its manpage for details.
35
36 FS::part_virtual_field inherits from FS::Record.  The following fields are 
37 currently supported:
38
39 =over 2
40
41 =item vfieldpart - primary key (assigned automatically)
42
43 =item name - name of the field
44
45 =item dbtable - table for which this virtual field is defined
46
47 =item check_block - Perl code to validate/normalize data
48
49 =item list_source - Perl code to generate a list of values (UI hint)
50
51 =item length - expected length of the value (UI hint)
52
53 =item label - descriptive label for the field (UI hint)
54
55 =item sequence - sort key (UI hint; unimplemented)
56
57 =back
58
59 =head1 BACKGROUND
60
61 "Form is none other than emptiness,
62  and emptiness is none other than form."
63 -- Heart Sutra
64
65 The virtual field mechanism allows site admins to make trivial changes to 
66 the Freeside database schema without modifying the code.  Specifically, the 
67 user can add custom-defined 'fields' to the set of data tracked by Freeside 
68 about objects such as customers and services.  These fields are not associated 
69 with any logic in the core Freeside system, but may be referenced in peripheral 
70 code such as exports, price calculations, or alternate interfaces, or may just 
71 be stored in the database for future reference.
72
73 This system was originally devised for svc_broadband, which (by necessity) 
74 comprises such a wide range of access technologies that no static set of fields 
75 could contain all the information needed by the exports.  In an appalling 
76 display of False Laziness, a parallel mechanism was implemented for the 
77 router table, to store properties such as passwords to configure routers.
78
79 The original system treated svc_broadband custom fields (sb_fields) as records 
80 in a completely separate table.  Any code that accessed or manipulated these 
81 fields had to be aware that they were I<not> fields in svc_broadband, but 
82 records in sb_field.  For example, code that inserted a svc_broadband with 
83 several custom fields had to create an FS::svc_broadband object, call its 
84 insert() method, and then create several FS::sb_field objects and call I<their>
85 insert() methods.
86
87 This created a problem for exports.  The insert method on any FS::svc_Common 
88 object (including svc_broadband) automatically triggers exports after the 
89 record has been inserted.  However, at this point, the sb_fields had not yet 
90 been inserted, so the export could not rely on their presence, which was the 
91 original purpose of sb_fields.
92
93 Hence the new system.  Virtual fields are appended to the field list of every 
94 record at the FS::Record level, whether the object is created ex nihilo with 
95 new() or fetched with qsearch().  The fields() method now returns a list of 
96 both real and virtual fields.  The insert(), replace(), and delete() methods 
97 now update both the base table and the virtual fields, in a single transaction.
98
99 A new method is provided, virtual_fields(), which gives only the virtual 
100 fields.  UI code that dynamically generates form widgets to edit virtual field
101 data should use this to figure out what fields are defined.  (See below.)
102
103 Subclasses may override virtual_fields() to restrict the set of virtual 
104 fields available.  Some discipline and sanity on the part of the programmer 
105 are required; in particular, this function should probably not depend on any 
106 fields in the record other than the primary key, since the others may change 
107 after the object is instantiated.  (Making it depend on I<virtual> fields is 
108 just asking for pain.)  One use of this is seen in FS::svc_Common; another 
109 possibility is field-level access control based on FS::UID::getotaker().
110
111 As a trivial case, a subclass may opt out of supporting virtual fields with 
112 the following code:
113
114 sub virtual_fields { () }
115
116 =head1 METHODS
117
118 =over 4
119
120 =item new HASHREF
121
122 Create a new record.  To add the record to the database, see "insert".
123
124 =cut
125
126 sub table { 'part_virtual_field'; }
127 sub virtual_fields { () }
128
129 =item insert
130
131 Adds this record to the database.  If there is an error, returns the error,
132 otherwise returns false.
133
134 =item delete
135
136 Deletes this record from the database.  If there is an error, returns the
137 error, otherwise returns false.
138
139 =item replace OLD_RECORD
140
141 Replaces OLD_RECORD with this one in the database.  If there is an error,
142 returns the error, otherwise returns false.
143
144 =item check
145
146 If there is an error, returns the error, otherwise returns false.
147 Called by the insert and replace methods.
148
149 =back
150
151 =cut
152
153 sub check {
154   my $self = shift;
155
156   my $error = $self->ut_text('name') ||
157               $self->ut_text('dbtable') ||
158               $self->ut_number('length')
159               ;
160   return $error if $error;
161
162   # Make sure it's a real table with a numeric primary key
163   my ($table, $pkey);
164   if($table = $FS::Record::dbdef->table($self->dbtable)) {
165     if($pkey = $table->primary_key) {
166       if($table->column($pkey)->type =~ /int/i) {
167         # this is what it should be
168       } else {
169         $error = "$table.$pkey is not an integer";
170       }
171     } else {
172       $error = "$table does not have a single-field primary key";
173     }
174   } else {
175     $error = "$table does not exist in the schema";
176   }
177   return $error if $error;
178
179   # Possibly some sanity checks for check_block and list_source?
180
181   $self->SUPER::check;  
182 }
183
184 =item list
185
186 Evaluates list_source.
187
188 =cut
189
190 sub list {
191   my $self = shift;
192   return () unless $self->list_source;
193
194   my @opts = eval($self->list_source);
195   if($@) {
196     warn $@;
197     return ();
198   } else {
199     return @opts;
200   }
201 }
202
203 =item widget UI_TYPE MODE [ VALUE ]
204
205 Generates UI code for a widget suitable for editing/viewing the field, based on 
206 list_source and length.  
207
208 The only UI_TYPE currently supported is 'HTML', and the only MODE is 'view'.
209 Others will be added later.
210
211 In HTML, all widgets are assumed to be table rows.  View widgets look like
212 <TR><TD ALIGN="right">Label</TD><TD BGCOLOR="#ffffff">Value</TD></TR>
213
214 (Most of the display style stuff, such as the colors, should probably go into 
215 a separate module specific to the UI.  That can wait, though.  The API for 
216 this function won't change.)
217
218 VALUE (optional) is the current value of the field.
219
220 =cut
221
222 sub widget {
223   my $self = shift;
224   my ($ui_type, $mode, $value) = @_;
225   my $text;
226   my $label = $self->label || $self->name;
227
228   if ($ui_type eq 'HTML') {
229     if ($mode eq 'view') {
230       $text = q!<TR><TD ALIGN="right">! . $label . 
231               q!</TD><TD BGCOLOR="#ffffff">! . $value .
232               q!</TD></TR>! . "\n";
233     } elsif ($mode eq 'edit') {
234       $text = q!<TR><TD ALIGN="right">! . $label .
235               q!</TD><TD>!;
236       if ($self->list_source) {
237         $text .= q!<SELECT NAME="! . $self->name . 
238                 q!" SIZE=1>! . "\n";
239         foreach ($self->list) {
240           $text .= q!<OPTION VALUE="! . $_ . q!"!;
241           $text .= ' SELECTED' if ($_ eq $value);
242           $text .= '>' . $_ . '</OPTION>' . "\n";
243         }
244       } else {
245         $text .= q!<INPUT NAME="! . $self->name .
246                 q!" VALUE="! . $value . q!"!;
247         if ($self->length) {
248           $text .= q! SIZE="! . $self->length . q!"!;
249         }
250         $text .= '>';
251       }
252       $text .= q!</TD></TR>! . "\n";
253     } else {
254       return '';
255     }
256   } else {
257     return '';
258   }
259   return $text;
260 }
261
262 =head1 VERSION
263
264 $Id: part_virtual_field.pm,v 1.2 2003-08-05 00:20:45 khoff Exp $
265
266 =head1 NOTES
267
268 =head2 Semantics of check_block:
269
270 This has been changed from the sb_field implementation to make check_blocks 
271 simpler and more natural to Perl programmers who work on things other than 
272 Freeside.
273
274 The check_block is eval'd with the (proposed) new value of the field in $_, 
275 and the object to be updated in $self.  Its return value is ignored.  The 
276 check_block may change the value of $_ to override the proposed value, or 
277 call die() (with an appropriate error message) to reject the update entirely;
278 the error string will be returned as the output of the check() method.
279
280 This makes check_blocks like
281
282 C<s/foo/bar/>
283
284 do what you expect.
285
286 The check_block is expected NOT to do anything freaky to $self, like modifying 
287 other fields or calling $self->check().  You have been warned.
288
289 (FIXME: Rewrite some of the warnings from part_sb_field and insert here.)
290
291 =head1 BUGS
292
293 None.  It's absolutely falwless.
294
295 =head1 SEE ALSO
296
297 L<FS::Record>, L<FS::virtual_field>
298
299 =cut
300
301 1;
302
303