Filename | /var/www/foswiki11/lib/Foswiki/Form.pm |
Statements | Executed 23 statements in 2.66ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 1.33ms | 1.39ms | BEGIN@42 | Foswiki::Form::
1 | 1 | 1 | 527µs | 599µs | BEGIN@43 | Foswiki::Form::
1 | 1 | 1 | 19µs | 46µs | BEGIN@32 | Foswiki::Form::
1 | 1 | 1 | 16µs | 30µs | BEGIN@33 | Foswiki::Form::
1 | 1 | 1 | 14µs | 206µs | BEGIN@39 | Foswiki::Form::
1 | 1 | 1 | 13µs | 38µs | BEGIN@38 | Foswiki::Form::
1 | 1 | 1 | 6µs | 6µs | BEGIN@44 | Foswiki::Form::
1 | 1 | 1 | 5µs | 5µs | BEGIN@35 | Foswiki::Form::
1 | 1 | 1 | 5µs | 5µs | BEGIN@41 | Foswiki::Form::
1 | 1 | 1 | 4µs | 4µs | BEGIN@45 | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | __ANON__[:305] | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | _extractPseudoFieldDefs | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | _link | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | _parseFormDefinition | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | createField | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | fieldTitle2FieldName | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | finish | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | getAvailableForms | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | getField | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | getFieldValuesFromQuery | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | getFields | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | isTextMergeable | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | new | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | renderForDisplay | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | renderForEdit | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | renderHidden | Foswiki::Form::
0 | 0 | 0 | 0s | 0s | stringify | Foswiki::Form::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # See bottom of file for license and copyright information | ||||
2 | |||||
3 | =begin TML | ||||
4 | |||||
5 | ---+ package Foswiki::Form | ||||
6 | |||||
7 | Object representing a single form definition. | ||||
8 | |||||
9 | Form definitions are mainly used to control rendering of a form for | ||||
10 | editing, though there is some application login there that handles | ||||
11 | transferring values between edits and saves. | ||||
12 | |||||
13 | A form definition consists of a Foswiki::Form object, which has a list | ||||
14 | of field definitions. Each field definition is an object of a type | ||||
15 | derived from Foswiki::Form::FieldDefinition. These objects are responsible | ||||
16 | for the actual syntax and semantics of the field type. Form definitions | ||||
17 | are parsed from Foswiki tables, and the types are mapped by name to a | ||||
18 | class declared in Foswiki::Form::* - for example, the =text= type is mapped | ||||
19 | to =Foswiki::Form::Text= and the =checkbox= type to =Foswiki::Form::Checkbox=. | ||||
20 | |||||
21 | The =Foswiki::Form::FieldDefinition= class declares default behaviours for | ||||
22 | types that accept a single value in their definitions. The | ||||
23 | =Foswiki::Form::ListFieldDefinition= extends this for types that have lists | ||||
24 | of possible values. | ||||
25 | |||||
26 | =cut | ||||
27 | |||||
28 | # The bulk of this object is a parser for form definitions. All the | ||||
29 | # intelligence is in the individual field types. | ||||
30 | |||||
31 | package Foswiki::Form; | ||||
32 | 2 | 41µs | 2 | 72µs | # spent 46µs (19+26) within Foswiki::Form::BEGIN@32 which was called:
# once (19µs+26µs) by Foswiki::Meta::renderFormForDisplay at line 32 # spent 46µs making 1 call to Foswiki::Form::BEGIN@32
# spent 26µs making 1 call to strict::import |
33 | 2 | 33µs | 2 | 44µs | # spent 30µs (16+14) within Foswiki::Form::BEGIN@33 which was called:
# once (16µs+14µs) by Foswiki::Meta::renderFormForDisplay at line 33 # spent 30µs making 1 call to Foswiki::Form::BEGIN@33
# spent 14µs making 1 call to warnings::import |
34 | |||||
35 | 2 | 46µs | 1 | 5µs | # spent 5µs within Foswiki::Form::BEGIN@35 which was called:
# once (5µs+0s) by Foswiki::Meta::renderFormForDisplay at line 35 # spent 5µs making 1 call to Foswiki::Form::BEGIN@35 |
36 | 1 | 8µs | our @ISA = ('Foswiki::Meta'); | ||
37 | |||||
38 | 2 | 35µs | 2 | 63µs | # spent 38µs (13+25) within Foswiki::Form::BEGIN@38 which was called:
# once (13µs+25µs) by Foswiki::Meta::renderFormForDisplay at line 38 # spent 38µs making 1 call to Foswiki::Form::BEGIN@38
# spent 25µs making 1 call to Assert::import |
39 | 2 | 35µs | 2 | 397µs | # spent 206µs (14+191) within Foswiki::Form::BEGIN@39 which was called:
# once (14µs+191µs) by Foswiki::Meta::renderFormForDisplay at line 39 # spent 206µs making 1 call to Foswiki::Form::BEGIN@39
# spent 191µs making 1 call to Error::import |
40 | |||||
41 | 2 | 20µs | 1 | 5µs | # spent 5µs within Foswiki::Form::BEGIN@41 which was called:
# once (5µs+0s) by Foswiki::Meta::renderFormForDisplay at line 41 # spent 5µs making 1 call to Foswiki::Form::BEGIN@41 |
42 | 2 | 106µs | 1 | 1.39ms | # spent 1.39ms (1.33+67µs) within Foswiki::Form::BEGIN@42 which was called:
# once (1.33ms+67µs) by Foswiki::Meta::renderFormForDisplay at line 42 # spent 1.39ms making 1 call to Foswiki::Form::BEGIN@42 |
43 | 2 | 99µs | 1 | 599µs | # spent 599µs (527+72) within Foswiki::Form::BEGIN@43 which was called:
# once (527µs+72µs) by Foswiki::Meta::renderFormForDisplay at line 43 # spent 599µs making 1 call to Foswiki::Form::BEGIN@43 |
44 | 2 | 21µs | 1 | 6µs | # spent 6µs within Foswiki::Form::BEGIN@44 which was called:
# once (6µs+0s) by Foswiki::Meta::renderFormForDisplay at line 44 # spent 6µs making 1 call to Foswiki::Form::BEGIN@44 |
45 | 2 | 2.20ms | 1 | 4µs | # spent 4µs within Foswiki::Form::BEGIN@45 which was called:
# once (4µs+0s) by Foswiki::Meta::renderFormForDisplay at line 45 # spent 4µs making 1 call to Foswiki::Form::BEGIN@45 |
46 | |||||
47 | # The following are reserved as URL parameters to scripts and may not be | ||||
48 | # used as field names in forms. | ||||
49 | 1 | 16µs | my %reservedFieldNames = map { $_ => 1 } | ||
50 | qw( action breaklock contenttype cover dontnotify editaction | ||||
51 | forcenewrevision formtemplate onlynewtopic onlywikiname | ||||
52 | originalrev skin templatetopic text topic topicparent user ); | ||||
53 | |||||
54 | =begin TML | ||||
55 | |||||
56 | ---++ ClassMethod new ( $session, $web, $topic, \@def ) | ||||
57 | |||||
58 | Looks up a form in the session object or, if it hasn't been read yet, | ||||
59 | reads it from the form definition topic on disc. | ||||
60 | * =$web= - default web to recover form from, if =$form= doesn't | ||||
61 | specify a web | ||||
62 | * =$topic= - name of the topic that contains the form definition | ||||
63 | * =\@def= - optional. A reference to a list of field definitions. | ||||
64 | If present, these definitions will be used, rather than any read from | ||||
65 | the form definition topic. | ||||
66 | |||||
67 | May throw Foswiki::OopsException if the web and form are not valid for use as a | ||||
68 | form name, or if \@def is not given and the form does not exist in the | ||||
69 | database. May throw Foswiki::AccessControlException if the form schema | ||||
70 | in the database is protected against view. | ||||
71 | |||||
72 | =cut | ||||
73 | |||||
74 | sub new { | ||||
75 | my ( $class, $session, $web, $form, $def ) = @_; | ||||
76 | |||||
77 | my ( $vweb, $vtopic ) = $session->normalizeWebTopicName( $web, $form ); | ||||
78 | my $this = $session->{forms}->{"$vweb.$vtopic"}; | ||||
79 | |||||
80 | unless ($this) { | ||||
81 | |||||
82 | # A form name has to be a valid topic name after normalisation | ||||
83 | $vweb = Foswiki::Sandbox::untaint( $vweb, | ||||
84 | \&Foswiki::Sandbox::validateWebName ); | ||||
85 | $vtopic = Foswiki::Sandbox::untaint( $vtopic, | ||||
86 | \&Foswiki::Sandbox::validateTopicName ); | ||||
87 | unless ( $vweb && $vtopic ) { | ||||
88 | throw Foswiki::OopsException( | ||||
89 | 'attention', | ||||
90 | def => 'invalid_form_name', | ||||
91 | web => $session->{webName}, | ||||
92 | topic => $session->{topicName}, | ||||
93 | params => [ $web, $form ] | ||||
94 | ); | ||||
95 | } | ||||
96 | |||||
97 | # Got to have either a def or a topic | ||||
98 | unless ( $def || $session->topicExists( $vweb, $vtopic ) ) { | ||||
99 | throw Foswiki::OopsException( | ||||
100 | 'attention', | ||||
101 | def => 'no_form_def', | ||||
102 | web => $session->{webName}, | ||||
103 | topic => $session->{topicName}, | ||||
104 | params => [ $vweb, $vtopic ] | ||||
105 | ); | ||||
106 | } | ||||
107 | |||||
108 | $this = $class->SUPER::new( $session, $vweb, $vtopic ); | ||||
109 | |||||
110 | unless ( $def || $this->haveAccess('VIEW') ) { | ||||
111 | throw Foswiki::AccessControlException( 'VIEW', $session->{user}, | ||||
112 | $vweb, $vtopic, $Foswiki::Meta::reason ); | ||||
113 | } | ||||
114 | |||||
115 | if ( ref($this) ne 'Foswiki::Form' ) { | ||||
116 | |||||
117 | #recast if we have to - allowing the cache to work its magic | ||||
118 | $this = bless( $this, 'Foswiki::Form' ); | ||||
119 | } | ||||
120 | |||||
121 | # cache the object before we've parsed it to prevent recursion | ||||
122 | #when there are SEARCH / INCLUDE macros in the form definition | ||||
123 | $session->{forms}->{"$vweb.$vtopic"} = $this; | ||||
124 | |||||
125 | unless ($def) { | ||||
126 | $this->{fields} = $this->_parseFormDefinition(); | ||||
127 | } | ||||
128 | elsif ( ref($def) eq 'ARRAY' ) { | ||||
129 | $this->{fields} = $def; | ||||
130 | } | ||||
131 | else { | ||||
132 | |||||
133 | # Foswiki::Meta object | ||||
134 | $this->{fields} = $this->_extractPseudoFieldDefs($def); | ||||
135 | } | ||||
136 | } | ||||
137 | |||||
138 | return $this; | ||||
139 | } | ||||
140 | |||||
141 | =begin TML | ||||
142 | |||||
143 | ---++ ObjectMethod finish() | ||||
144 | Break circular references. | ||||
145 | |||||
146 | =cut | ||||
147 | |||||
148 | # Note to developers; please undef *all* fields in the object explicitly, | ||||
149 | # whether they are references or not. That way this method is "golden | ||||
150 | # documentation" of the live fields in the object. | ||||
151 | sub finish { | ||||
152 | my $this = shift; | ||||
153 | foreach ( @{ $this->{fields} } ) { | ||||
154 | $_->finish(); | ||||
155 | } | ||||
156 | undef $this->{fields}; | ||||
157 | $this->SUPER::finish(); | ||||
158 | } | ||||
159 | |||||
160 | =begin TML | ||||
161 | |||||
162 | ---++ StaticMethod getAvailableForms( $metaObject ) -> @forms | ||||
163 | |||||
164 | Get a list of the names of forms that are available for use in the | ||||
165 | given topic. $metaObject can be a topic or a web. | ||||
166 | |||||
167 | =cut | ||||
168 | |||||
169 | sub getAvailableForms { | ||||
170 | my $metaObject = shift; | ||||
171 | if ( defined $metaObject->topic ) { | ||||
172 | $metaObject = | ||||
173 | Foswiki::Meta->new( $metaObject->session, $metaObject->web ); | ||||
174 | } | ||||
175 | my $legalForms = $metaObject->getPreference('WEBFORMS') || ''; | ||||
176 | $legalForms =~ s/^\s+//; | ||||
177 | $legalForms =~ s/\s+$//; | ||||
178 | my @forms = split( /[,\s]+/, $legalForms ); | ||||
179 | |||||
180 | # This is where we could %SEARCH for *Form topics | ||||
181 | return @forms; | ||||
182 | } | ||||
183 | |||||
184 | =begin TML | ||||
185 | |||||
186 | ---++ StaticMethod fieldTitle2FieldName($title) -> $name | ||||
187 | Chop out all except A-Za-z0-9_. from a field name to create a | ||||
188 | valid "name" for storing in meta-data | ||||
189 | |||||
190 | =cut | ||||
191 | |||||
192 | sub fieldTitle2FieldName { | ||||
193 | my ($text) = @_; | ||||
194 | return '' unless defined($text); | ||||
195 | $text =~ s/!//g; | ||||
196 | $text =~ s/<nop>//g; # support <nop> character in title | ||||
197 | $text =~ s/[^A-Za-z0-9_\.]//g; | ||||
198 | return $text; | ||||
199 | } | ||||
200 | |||||
201 | # Get definition from supplied topic text | ||||
202 | # Returns array of arrays | ||||
203 | # 1st - list fields | ||||
204 | # 2nd - name, title, type, size, vals, tooltip, attributes | ||||
205 | # Possible attributes are "M" (mandatory field) | ||||
206 | sub _parseFormDefinition { | ||||
207 | my $this = shift; | ||||
208 | |||||
209 | my @fields = (); | ||||
210 | my $inBlock = 0; | ||||
211 | my $text = $this->text(); | ||||
212 | $text = '' unless defined $text; | ||||
213 | |||||
214 | $text =~ s/\\\n//g; # remove trailing '\' and join continuation lines | ||||
215 | |||||
216 | # | *Name:* | *Type:* | *Size:* | *Value:* | *Tooltip message:* | *Attributes:* | | ||||
217 | # Tooltip and attributes are optional | ||||
218 | foreach my $line ( split( /\n/, $text ) ) { | ||||
219 | if ( $line =~ /^\s*\|.*Name[^|]*\|.*Type[^|]*\|.*Size[^|]*\|/ ) { | ||||
220 | $inBlock = 1; | ||||
221 | next; | ||||
222 | } | ||||
223 | |||||
224 | # Only insist on first field being present FIXME - use oops page instead? | ||||
225 | if ( $inBlock && $line =~ s/^\s*\|\s*// ) { | ||||
226 | $line =~ s/\\\|/\007/g; # protect \| from split | ||||
227 | my ( $title, $type, $size, $vals, $tooltip, $attributes ) = | ||||
228 | map { s/\007/|/g; $_ } split( /\s*\|\s*/, $line ); | ||||
229 | |||||
230 | $title ||= ''; | ||||
231 | |||||
232 | $type ||= ''; | ||||
233 | $type = lc($type); | ||||
234 | $type =~ s/^\s*//go; | ||||
235 | $type =~ s/\s*$//go; | ||||
236 | $type = 'text' if ( !$type ); | ||||
237 | |||||
238 | $size ||= ''; | ||||
239 | |||||
240 | $vals ||= ''; | ||||
241 | $vals = $this->expandMacros($vals); | ||||
242 | $vals =~ s/<\/?(!|nop|noautolink)\/?>//go; | ||||
243 | $vals =~ s/^\s+//g; | ||||
244 | $vals =~ s/\s+$//g; | ||||
245 | |||||
246 | $tooltip ||= ''; | ||||
247 | |||||
248 | $attributes ||= ''; | ||||
249 | $attributes =~ s/\s*//go; | ||||
250 | $attributes = '' if ( !$attributes ); | ||||
251 | |||||
252 | my $definingTopic = ""; | ||||
253 | if ( $title =~ /\[\[(.+)\]\[(.+)\]\]/ ) { | ||||
254 | |||||
255 | # use common defining topics with different field titles | ||||
256 | $definingTopic = fieldTitle2FieldName($1); | ||||
257 | $title = $2; | ||||
258 | } | ||||
259 | |||||
260 | my $name = fieldTitle2FieldName($title); | ||||
261 | |||||
262 | # Rename fields with reserved names | ||||
263 | if ( $reservedFieldNames{$name} ) { | ||||
264 | $name .= '_'; | ||||
265 | } | ||||
266 | my $fieldDef = $this->createField( | ||||
267 | $type, | ||||
268 | name => $name, | ||||
269 | title => $title, | ||||
270 | size => $size, | ||||
271 | value => $vals, | ||||
272 | tooltip => $tooltip, | ||||
273 | attributes => $attributes, | ||||
274 | definingTopic => $definingTopic, | ||||
275 | web => $this->web(), | ||||
276 | topic => $this->topic() | ||||
277 | ); | ||||
278 | push( @fields, $fieldDef ); | ||||
279 | |||||
280 | $this->{mandatoryFieldsPresent} ||= $fieldDef->isMandatory(); | ||||
281 | } | ||||
282 | else { | ||||
283 | $inBlock = 0; | ||||
284 | } | ||||
285 | } | ||||
286 | |||||
287 | return \@fields; | ||||
288 | } | ||||
289 | |||||
290 | # PROTECTED | ||||
291 | # Create a field object. Done like this so that this method can be | ||||
292 | # overridden by subclasses to extend the range of field types. | ||||
293 | sub createField { | ||||
294 | my $this = shift; | ||||
295 | my $type = shift; | ||||
296 | |||||
297 | # The untaint is required for the validation *and* the ucfirst, which | ||||
298 | # retaints when use locale is in force | ||||
299 | my $class = Foswiki::Sandbox::untaint( | ||||
300 | $type, | ||||
301 | sub { | ||||
302 | my $class = shift; | ||||
303 | $class =~ /^(\w*)/; # cut off +buttons etc | ||||
304 | return 'Foswiki::Form::' . ucfirst($1); | ||||
305 | } | ||||
306 | ); | ||||
307 | eval 'require ' . $class; | ||||
308 | if ($@) { | ||||
309 | $this->session->logger->log( 'error', | ||||
310 | "error compiling class $class: $@" ); | ||||
311 | |||||
312 | # Type not available; use base type | ||||
313 | require Foswiki::Form::FieldDefinition; | ||||
314 | $class = 'Foswiki::Form::FieldDefinition'; | ||||
315 | } | ||||
316 | return $class->new( session => $this->session(), type => $type, @_ ); | ||||
317 | } | ||||
318 | |||||
319 | # Generate a link to the given topic, so we can bring up details in a | ||||
320 | # separate window. | ||||
321 | sub _link { | ||||
322 | my ( $this, $string, $tooltip, $topic ) = @_; | ||||
323 | |||||
324 | $string =~ s/[\[\]]//go; | ||||
325 | |||||
326 | $topic ||= $string; | ||||
327 | my $defaultToolTip = | ||||
328 | $this->session->i18n->maketext('Details in separate window'); | ||||
329 | $tooltip ||= $defaultToolTip; | ||||
330 | |||||
331 | ( my $web, $topic ) = | ||||
332 | $this->session->normalizeWebTopicName( $this->{web}, $topic ); | ||||
333 | |||||
334 | $web = | ||||
335 | Foswiki::Sandbox::untaint( $web, \&Foswiki::Sandbox::validateWebName ); | ||||
336 | |||||
337 | $topic = Foswiki::Sandbox::untaint( $topic, | ||||
338 | \&Foswiki::Sandbox::validateTopicName ); | ||||
339 | |||||
340 | my $link; | ||||
341 | |||||
342 | if ( $this->session->topicExists( $web, $topic ) ) { | ||||
343 | $link = CGI::a( | ||||
344 | { | ||||
345 | target => $topic, | ||||
346 | title => $tooltip, | ||||
347 | href => $this->session->getScriptUrl( 0, 'view', $web, $topic ), | ||||
348 | rel => 'nofollow' | ||||
349 | }, | ||||
350 | $string | ||||
351 | ); | ||||
352 | } | ||||
353 | else { | ||||
354 | my $that = | ||||
355 | Foswiki::Meta->new( $this->session, $web, | ||||
356 | $topic || $Foswiki::cfg{HomeTopicName} ); | ||||
357 | my $expanded = $that->expandMacros($string); | ||||
358 | if ( $tooltip ne $defaultToolTip ) { | ||||
359 | $link = CGI::span( { title => $tooltip }, $expanded ); | ||||
360 | } | ||||
361 | else { | ||||
362 | $link = $expanded; | ||||
363 | } | ||||
364 | } | ||||
365 | |||||
366 | return $link; | ||||
367 | } | ||||
368 | |||||
369 | sub stringify { | ||||
370 | my $this = shift; | ||||
371 | my $fs = "| *Name* | *Type* | *Size* | *Attributes* |\n"; | ||||
372 | foreach my $fieldDef ( @{ $this->{fields} } ) { | ||||
373 | $fs .= $fieldDef->stringify(); | ||||
374 | } | ||||
375 | return $fs; | ||||
376 | } | ||||
377 | |||||
378 | =begin TML | ||||
379 | |||||
380 | ---++ ObjectMethod renderForEdit( $topicObject ) -> $html | ||||
381 | |||||
382 | * =$topicObject= the topic being rendered | ||||
383 | |||||
384 | Render the form fields for entry during an edit session, using data values | ||||
385 | from $meta | ||||
386 | |||||
387 | =cut | ||||
388 | |||||
389 | sub renderForEdit { | ||||
390 | my ( $this, $topicObject ) = @_; | ||||
391 | ASSERT( $topicObject->isa('Foswiki::Meta') ) if DEBUG; | ||||
392 | require CGI; | ||||
393 | my $session = $this->session; | ||||
394 | |||||
395 | if ( $this->{mandatoryFieldsPresent} ) { | ||||
396 | $session->enterContext('mandatoryfields'); | ||||
397 | } | ||||
398 | my $tmpl = $session->templates->readTemplate('form'); | ||||
399 | $tmpl = $topicObject->expandMacros($tmpl); | ||||
400 | |||||
401 | $tmpl =~ s/%FORMTITLE%/$this->_link( $this->web.'.'.$this->topic )/ge; | ||||
402 | my ( $text, $repeatTitledText, $repeatUntitledText, $afterText ) = | ||||
403 | split( /%REPEAT%/, $tmpl ); | ||||
404 | |||||
405 | foreach my $fieldDef ( @{ $this->{fields} } ) { | ||||
406 | |||||
407 | my $value; | ||||
408 | my $tooltip = $fieldDef->{tooltip}; | ||||
409 | my $definingTopic = $fieldDef->{definingTopic}; | ||||
410 | my $title = $fieldDef->{title}; | ||||
411 | my $tmp = ''; | ||||
412 | if ( !$title && !$fieldDef->isEditable() ) { | ||||
413 | |||||
414 | # Special handling for untitled labels. | ||||
415 | # SMELL: Assumes that uneditable fields are not multi-valued | ||||
416 | $tmp = $repeatUntitledText; | ||||
417 | $value = | ||||
418 | $topicObject->renderTML( | ||||
419 | $topicObject->expandMacros( $fieldDef->{value} ) ); | ||||
420 | } | ||||
421 | else { | ||||
422 | $tmp = $repeatTitledText; | ||||
423 | |||||
424 | if ( defined( $fieldDef->{name} ) ) { | ||||
425 | my $field = $topicObject->get( 'FIELD', $fieldDef->{name} ); | ||||
426 | $value = $field->{value}; | ||||
427 | } | ||||
428 | my $extra = ''; # extras on col 0 | ||||
429 | |||||
430 | unless ( defined($value) ) { | ||||
431 | my $dv = $fieldDef->getDefaultValue($value); | ||||
432 | if ( defined($dv) ) { | ||||
433 | $dv = $topicObject->expandMacros($dv); | ||||
434 | $value = Foswiki::expandStandardEscapes($dv); # Item2837 | ||||
435 | } | ||||
436 | } | ||||
437 | |||||
438 | # Give plugin field types a chance first (but no chance to add to | ||||
439 | # col 0 :-( | ||||
440 | # SMELL: assumes that the field value is a string | ||||
441 | my $output = $session->{plugins}->dispatch( | ||||
442 | 'renderFormFieldForEditHandler', $fieldDef->{name}, | ||||
443 | $fieldDef->{type}, $fieldDef->{size}, | ||||
444 | $value, $fieldDef->{attributes}, | ||||
445 | $fieldDef->{value} | ||||
446 | ); | ||||
447 | |||||
448 | if ($output) { | ||||
449 | $value = $output; | ||||
450 | } | ||||
451 | else { | ||||
452 | ( $extra, $value ) = | ||||
453 | $fieldDef->renderForEdit( $topicObject, $value ); | ||||
454 | } | ||||
455 | |||||
456 | if ( $fieldDef->isMandatory() ) { | ||||
457 | $extra .= CGI::span( { class => 'foswikiAlert' }, ' *' ); | ||||
458 | } | ||||
459 | |||||
460 | $tmp =~ s/%ROWTITLE%/ | ||||
461 | $this->_link( $title, $tooltip, $definingTopic )/ge; | ||||
462 | $tmp =~ s/%ROWEXTRA%/$extra/g; | ||||
463 | } | ||||
464 | $tmp =~ s/%ROWVALUE%/$value/g; | ||||
465 | $text .= $tmp; | ||||
466 | } | ||||
467 | |||||
468 | $text .= $afterText; | ||||
469 | return $text; | ||||
470 | } | ||||
471 | |||||
472 | =begin TML | ||||
473 | |||||
474 | ---++ ObjectMethod renderHidden( $topicObject ) -> $html | ||||
475 | |||||
476 | Render form fields found in the meta as hidden inputs, so they pass | ||||
477 | through edits untouched. | ||||
478 | |||||
479 | =cut | ||||
480 | |||||
481 | sub renderHidden { | ||||
482 | my ( $this, $topicObject ) = @_; | ||||
483 | ASSERT( $topicObject->isa('Foswiki::Meta') ) if DEBUG; | ||||
484 | |||||
485 | my $text = ''; | ||||
486 | |||||
487 | foreach my $field ( @{ $this->{fields} } ) { | ||||
488 | $text .= $field->renderHidden($topicObject); | ||||
489 | } | ||||
490 | |||||
491 | return $text; | ||||
492 | } | ||||
493 | |||||
494 | =begin TML | ||||
495 | |||||
496 | ---++ ObjectMethod getFieldValuesFromQuery($query, $topicObject) -> ( $seen, \@missing ) | ||||
497 | |||||
498 | Extract new values for form fields from a query. | ||||
499 | |||||
500 | * =$query= - the query | ||||
501 | * =$topicObject= - the meta object that is storing the form values | ||||
502 | |||||
503 | For each field, if there is a value in the query, use it. | ||||
504 | Otherwise if there is already entry for the field in the meta, keep it. | ||||
505 | |||||
506 | Returns the number of fields which had values provided by the query, | ||||
507 | and a references to an array of the names of mandatory fields that were | ||||
508 | missing from the query. | ||||
509 | |||||
510 | =cut | ||||
511 | |||||
512 | sub getFieldValuesFromQuery { | ||||
513 | my ( $this, $query, $topicObject ) = @_; | ||||
514 | ASSERT( $topicObject->isa('Foswiki::Meta') ) if DEBUG; | ||||
515 | my @missing; | ||||
516 | my $seen = 0; | ||||
517 | |||||
518 | # Remove the old defs so we apply the | ||||
519 | # order in the form definition, and not the | ||||
520 | # order in the previous meta object. See Item1982. | ||||
521 | my @old = $topicObject->find('FIELD'); | ||||
522 | $topicObject->remove('FIELD'); | ||||
523 | foreach my $fieldDef ( @{ $this->{fields} } ) { | ||||
524 | my ( $set, $present ) = | ||||
525 | $fieldDef->populateMetaFromQueryData( $query, $topicObject, \@old ); | ||||
526 | if ($present) { | ||||
527 | $seen++; | ||||
528 | } | ||||
529 | if ( !$set && $fieldDef->isMandatory() ) { | ||||
530 | |||||
531 | # Remember missing mandatory fields | ||||
532 | push( @missing, $fieldDef->{title} || "unnamed field" ); | ||||
533 | } | ||||
534 | } | ||||
535 | return ( $seen, \@missing ); | ||||
536 | } | ||||
537 | |||||
538 | =begin TML | ||||
539 | |||||
540 | ---++ ObjectMethod isTextMergeable( $name ) -> $boolean | ||||
541 | |||||
542 | * =$name= - name of a form field (value of the =name= attribute) | ||||
543 | |||||
544 | Returns true if the type of the named field allows it to be text-merged. | ||||
545 | |||||
546 | If the form does not define the field, it is assumed to be mergeable. | ||||
547 | |||||
548 | =cut | ||||
549 | |||||
550 | sub isTextMergeable { | ||||
551 | my ( $this, $name ) = @_; | ||||
552 | |||||
553 | my $fieldDef = $this->getField($name); | ||||
554 | if ($fieldDef) { | ||||
555 | return $fieldDef->isTextMergeable(); | ||||
556 | } | ||||
557 | |||||
558 | # Field not found - assume it is mergeable | ||||
559 | return 1; | ||||
560 | } | ||||
561 | |||||
562 | =begin TML | ||||
563 | |||||
564 | ---++ ObjectMethod getField( $name ) -> $fieldDefinition | ||||
565 | |||||
566 | * =$name= - name of a form field (value of the =name= attribute) | ||||
567 | |||||
568 | Returns a =Foswiki::Form::FieldDefinition=, or undef if the form does not | ||||
569 | define the field. | ||||
570 | |||||
571 | =cut | ||||
572 | |||||
573 | sub getField { | ||||
574 | my ( $this, $name ) = @_; | ||||
575 | foreach my $fieldDef ( @{ $this->{fields} } ) { | ||||
576 | return $fieldDef if ( $fieldDef->{name} && $fieldDef->{name} eq $name ); | ||||
577 | } | ||||
578 | return; | ||||
579 | } | ||||
580 | |||||
581 | =begin TML | ||||
582 | |||||
583 | ---++ ObjectMethod getFields() -> \@fields | ||||
584 | |||||
585 | Return a list containing references to field name/value pairs. | ||||
586 | Each entry in the list has a {name} field and a {value} field. It may | ||||
587 | have other fields as well, which caller should ignore. The | ||||
588 | returned list should be treated as *read only* (must not be written to). | ||||
589 | |||||
590 | =cut | ||||
591 | |||||
592 | sub getFields { | ||||
593 | my $this = shift; | ||||
594 | return $this->{fields}; | ||||
595 | } | ||||
596 | |||||
597 | sub renderForDisplay { | ||||
598 | my ( $this, $topicObject ) = @_; | ||||
599 | |||||
600 | my $templates = $this->session->templates; | ||||
601 | $templates->readTemplate('formtables'); | ||||
602 | |||||
603 | my $text = $templates->expandTemplate('FORM:display:header'); | ||||
604 | |||||
605 | my $rowTemplate = $templates->expandTemplate('FORM:display:row'); | ||||
606 | my $hasAllFieldsHidden = 1; | ||||
607 | foreach my $fieldDef ( @{ $this->{fields} } ) { | ||||
608 | my $fm = $topicObject->get( 'FIELD', $fieldDef->{name} ); | ||||
609 | next unless $fm; | ||||
610 | my $fa = $fm->{attributes} || ''; | ||||
611 | unless ( $fa =~ /H/ ) { | ||||
612 | $hasAllFieldsHidden = 0; | ||||
613 | my $row = $rowTemplate; | ||||
614 | |||||
615 | # Legacy; was %A_TITLE% before it was $title | ||||
616 | $row =~ s/%A_TITLE%/\$title/g; | ||||
617 | $row =~ s/%A_VALUE%/\$value/g; # Legacy | ||||
618 | |||||
619 | # display => 1 gets mapped values (rather than raw) | ||||
620 | $text .= | ||||
621 | $fieldDef->renderForDisplay( $row, $fm->{value}, | ||||
622 | { display => 1 } ); | ||||
623 | } | ||||
624 | } | ||||
625 | return '' if $hasAllFieldsHidden; | ||||
626 | |||||
627 | $text .= $templates->expandTemplate('FORM:display:footer'); | ||||
628 | |||||
629 | # substitute remaining placeholders in footer and header | ||||
630 | $text =~ s/%A_TITLE%/$this->getPath()/ge; | ||||
631 | |||||
632 | return $text; | ||||
633 | } | ||||
634 | |||||
635 | # extractPseudoFieldDefs( $meta ) -> $fieldDefs | ||||
636 | # Examine the FIELDs in $meta and reverse-engineer a set of field | ||||
637 | # definitions that can be used to construct a new "pseudo-form". This | ||||
638 | # fake form can be used to support editing of topics that have an attached | ||||
639 | # form that has no definition topic. | ||||
640 | sub _extractPseudoFieldDefs { | ||||
641 | my ( $this, $meta ) = @_; | ||||
642 | my @fields = $meta->find('FIELD'); | ||||
643 | my @fieldDefs; | ||||
644 | require Foswiki::Form::FieldDefinition; | ||||
645 | foreach my $field (@fields) { | ||||
646 | |||||
647 | # Fields are name, value, title, but there is no other type | ||||
648 | # information so we have to treat them all as "text" :-( | ||||
649 | my $fieldDef = new Foswiki::Form::FieldDefinition( | ||||
650 | session => $this->session, | ||||
651 | name => $field->{name}, | ||||
652 | title => $field->{title} || $field->{name}, | ||||
653 | attributes => $field->{attributes} || '' | ||||
654 | ); | ||||
655 | push( @fieldDefs, $fieldDef ); | ||||
656 | } | ||||
657 | return \@fieldDefs; | ||||
658 | } | ||||
659 | |||||
660 | 1 | 6µs | 1; | ||
661 | __END__ |