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