svc_broadband rewrite
[freeside.git] / FS / FS / part_sb_field.pm
1 package FS::part_sb_field;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::Record qw( qsearchs );
6
7 @ISA = qw( FS::Record );
8
9 =head1 NAME
10
11 FS::part_sb_field - Object methods for part_sb_field records
12
13 =head1 SYNOPSIS
14
15   use FS::part_sb_field;
16
17   $record = new FS::part_sb_field \%hash;
18   $record = new FS::part_sb_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_sb_field object represents an extended field (xfield) definition 
31 for svc_broadband's sb_field mechanism (see L<FS::svc_broadband>).  
32 FS::part_sb_field inherits from FS::Record.  The following fields are 
33 currently supported:
34
35 =over 2
36
37 =item sbfieldpart - primary key (assigned automatically)
38
39 =item name - name of the field
40
41 =item svcpart - service type for which this field is available (see L<FS::part_svc>)
42
43 =item length - length of the contents of the field (see note #1)
44
45 =item check_block - validation routine (see note #2)
46
47 =item list_source - enumeration routine (see note #3)
48
49 =back
50
51 =head1 BACKGROUND
52
53 Broadband services, unlike dialup services, are provided over a wide 
54 variety of physical media (DSL, wireless, cable modems, digital circuits) 
55 and network architectures (Ethernet, PPP, ATM).  For many of these access 
56 mechanisms, adding a new customer requires knowledge of some properties 
57 of the physical connection (circuit number, the type of CPE in use, etc.).
58 It is unreasonable to expect ISPs to alter Freeside's schema (and the 
59 associated library and UI code) to make each of these parameters a field in 
60 svc_broadband.
61
62 Hence sb_field and part_sb_field.  They allow the Freeside administrator to
63 define 'extended fields' ('xfields') associated with svc_broadband records.
64 These are I<not> processed in any way by Freeside itself; they exist solely for
65 use by exports (see L<FS::part_export>) and technical support staff.
66
67 For a parallel mechanism (at the per-router level rather than per-service), 
68 see L<FS::part_router_field>.
69
70 =head1 METHODS
71
72 =over 4
73
74 =item new HASHREF
75
76 Create a new record.  To add the record to the database, see "insert".
77
78 =cut
79
80 sub table { 'part_sb_field'; }
81
82 =item insert
83
84 Adds this record to the database.  If there is an error, returns the error,
85 otherwise returns false.
86
87 =item delete
88
89 Deletes this record from the database.  If there is an error, returns the
90 error, otherwise returns false.
91
92 =item replace OLD_RECORD
93
94 Replaces OLD_RECORD with this one in the database.  If there is an error,
95 returns the error, otherwise returns false.
96
97 =item check
98
99 Checks all fields to make sure this is a valid record.  If there is an error,
100 returns the error, otherwise returns false.  Called by the insert and replace
101 methods.
102
103 =cut
104
105 sub check {
106   my $self = shift;
107   my $error = '';
108
109   $error = $self->ut_numbern('svcpart');
110   return $error if $error;
111
112   unless (qsearchs('part_svc', { svcpart => $self->svcpart }))
113     { return "Unknown svcpart: " . $self->svcpart;}
114
115   $self->name =~ /^([a-z0-9_\-\.]{1,15})$/i
116     or return "Invalid field name for part_sb_field";
117
118   #How to check input_block, display_block, and check_block?
119
120   ''; #no error
121 }
122
123 =item list_values
124
125 If the I<list_source> field is set, this method eval()s it and 
126 returns its output.  If the field is empty, list_values returns 
127 an empty list.
128
129 Any arguments passed to this method will be received by the list_source 
130 code, but this behavior is a fortuitous accident and may be removed in 
131 the future.
132
133 =cut
134
135 sub list_values {
136   my $self = shift;
137   return () unless $self->list_source;
138
139   my @opts = eval($self->list_source);
140   if($@) {
141     warn $@;
142     return ();
143   } else {
144     return @opts;
145   }
146 }
147
148 =item part_svc
149
150 Returns the FS::part_svc object associated with this field definition.
151
152 =cut
153
154 sub part_svc {
155   my $self = shift;
156   return qsearchs('part_svc', { svcpart => $self->svcpart });
157 }
158
159 =back
160
161 =head1 VERSION
162
163 $Id: 
164
165 =head1 NOTES
166
167 =over
168
169 =item 1.
170
171 The I<length> field is not enforced.  It provides a hint to UI
172 code about how to display the field on a form.  If you want to enforce a
173 minimum or maximum length for a field, use a I<check_block>.
174
175 =item 2.
176
177 The check_block mechanism used here as well as in
178 FS::part_router_field allows the user to define validation rules.
179
180 When FS::sb_field::check is called, the proposed value of the xfield is
181 assigned to $_.  The check_block is then eval()'d and its return value
182 captured.  If the return value is false (empty/zero/undef), $_ is then assigned
183 back into the field and stored in the database.
184
185 Therefore a check_block can do three different things with the value: allow
186 it, allow it with a modification, or reject it.  This is very flexible, but
187 somewhat dangerous.  Some warnings:
188
189 =over 2
190
191 =item *
192
193 Assume that $_ has had I<no> error checking prior to the
194 check_block.  That's what the check_block is for, after all.  It could
195 contain I<anything>: evil shell commands in backquotes, 100kb JPEG images,
196 the Klez virus, whatever.
197
198 =item *
199
200 If your check_block modifies the input value, it should probably
201 produce a value that wouldn't be modified by going through the same
202 check_block again.  (That is, it should map input values into its own
203 eigenspace.)  The reason is that if someone calls $new->replace($old),
204 where $new and $old contain the same value for the field, they probably
205 want the field to keep its old value, not to get transformed by the
206 check_block again.  So don't do silly things like '$_++' or
207 'tr/A-Za-z/a-zA-Z/'.
208
209 =item *
210
211 Don't alter the contents of the database.  I<Reading> the database
212 is perfectly reasonable, but writing to it is a bad idea.  Remember that
213 check() might get called more than once, as described above.
214
215 =item *
216
217 The check_block probably won't even get called if the user submits
218 an I<empty> sb_field.  So at present, you can't set up a default value with
219 something like 's/^$/foo/'.  Conversely, don't replace the submitted value
220 with an empty string.  It probably will get stored, but might be deleted at
221 any time.
222
223 =back
224
225 =item 3.
226
227 The list_source mechanism is a UI hint (like length) to generate
228 drop-down or list boxes.  If list_source contains a value, the UI code can
229 eval() it and use the results as the options on the list.
230
231 Note 'can'.  This is not a substitute for check_block.  The HTML interface
232 currently requires that the user pick one of the options on the list
233 because that's the way HTML drop-down boxes work, but in the future the UI
234 code might add an 'Other (please specify)' option and a text box so that
235 the user can enter something else.  Or it might ignore list_source and just
236 generate a text box.  Or the interface might be rewritten in MS Access,
237 where drop-down boxes have text boxes built in.  Data validation is the job
238 of check(), not the front end.
239
240 Note also that a list of literals evaluates to itself, so a list_source
241 like
242
243 C<('Windows', 'MacOS', 'Linux')>
244
245 or
246
247 C<qw(Windows MacOS Linux)>
248
249 means exactly what you'd think.
250
251 =head1 BUGS
252
253 The lack of any way to do default values.  We might add this as another UI
254 hint (since, for the most part, it's the UI's job to figure out which fields
255 have had values entered into them).  In fact, there are lots of things we
256 should add as UI hints.
257
258 Oh, and the documentation is probably full of lies.
259
260 =head1 SEE ALSO
261
262 FS::svc_broadband, FS::sb_field, schema.html from the base documentation.
263
264 =cut
265
266 1;
267