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