1
# See bottom of file for license and copyright details
5
---+ package Foswiki::Form
7
Object representing a single form definition.
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.
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=.
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
28
# The bulk of this object is a parser for form definitions. All the
29
# intelligence is in the individual field types.
31
package Foswiki::Form;
37
# The following are reserved as URL parameters to scripts and may not be
38
# used as field names in forms.
39
my %reservedFieldNames = map { $_ => 1 }
40
qw( action breaklock contenttype cover dontnotify editaction
41
forcenewrevision formtemplate onlynewtopic onlywikiname
42
originalrev skin templatetopic text topic topicparent user );
46
---++ ClassMethod new ( $session, $web, $form, \@def )
48
Looks up a form in the session object or, if it hasn't been read yet,
49
reads it from the form definition topic on disc.
50
* =$web= - default web to recover form from, if =$form= doesn't
52
* =$form= - name of the form
53
* =\@def= - optional. A reference to a list of field definitions.
54
If present, these definitions will be used, rather than any read from
55
the form definition topic. Note that this array should not be modified
56
again after being passed into this constructor (it is not copied).
58
If the form cannot be read, will return undef to allow the caller to take
64
my ( $class, $session, $web, $form, $def ) = @_;
66
( $web, $form ) = $session->normalizeWebTopicName( $web, $form );
70
Foswiki::Sandbox::untaint( $web, \&Foswiki::Sandbox::validateWebName );
72
Foswiki::Sandbox::untaint( $form, \&Foswiki::Sandbox::validateTopicName );
74
unless ( $web && $form ) {
78
my $this = $session->{forms}->{"$web.$form"};
90
$session->{forms}->{"$web.$form"} = $this;
94
my $store = $session->{store};
96
# Read topic that defines the form
97
if ( $store->topicExists( $web, $form ) ) {
99
$store->readTopic( $session->{user}, $web, $form, undef );
101
$this->{fields} = _parseFormDefinition( $this, $meta, $text );
104
delete $session->{forms}->{"$web.$form"};
108
elsif ( ref($def) eq 'ARRAY' ) {
109
$this->{fields} = $def;
113
# Foswiki::Meta object
114
$this->{fields} = $this->_extractPseudoFieldDefs($def);
123
---++ ObjectMethod finish()
124
Break circular references.
128
# Note to developers; please undef *all* fields in the object explicitly,
129
# whether they are references or not. That way this method is "golden
130
# documentation" of the live fields in the object.
134
undef $this->{topic};
135
foreach ( @{ $this->{fields} } ) {
138
undef $this->{fields};
139
undef $this->{session};
144
---++ StaticMethod fieldTitle2FieldName($title) -> $name
145
Chop out all except A-Za-z0-9_. from a field name to create a
146
valid "name" for storing in meta-data
150
sub fieldTitle2FieldName {
152
return '' unless defined($text);
153
$text =~ s/<nop>//g; # support <nop> character in title
154
$text =~ s/[^A-Za-z0-9_\.]//g;
158
# Get definition from supplied topic text
159
# Returns array of arrays
161
# 2nd - name, title, type, size, vals, tooltip, attributes
162
# Possible attributes are "M" (mandatory field)
163
sub _parseFormDefinition {
164
my ( $this, $meta, $text ) = @_;
166
my $store = $this->{session}->{store};
170
$text =~ s/\\\n//g; # remove trailing '\' and join continuation lines
172
# | *Name:* | *Type:* | *Size:* | *Value:* | *Tooltip message:* | *Attributes:* |
173
# Tooltip and attributes are optional
174
foreach my $line ( split( /\n/, $text ) ) {
175
if ( $line =~ /^\s*\|.*Name[^|]*\|.*Type[^|]*\|.*Size[^|]*\|/ ) {
180
# Only insist on first field being present FIXME - use oops page instead?
181
if ( $inBlock && $line =~ s/^\s*\|\s*// ) {
182
$line =~ s/\\\|/\007/g; # protect \| from split
183
my ( $title, $type, $size, $vals, $tooltip, $attributes ) =
184
map { s/\007/|/g; $_ } split( /\s*\|\s*/, $line );
192
$type = 'text' if ( !$type );
199
->handleCommonTags( $vals, $this->{web}, $this->{topic}, $meta );
200
$vals =~ s/<\/?(nop|noautolink)\/?>//go;
207
$attributes =~ s/\s*//go;
208
$attributes = '' if ( !$attributes );
210
my $definingTopic = "";
211
if ( $title =~ /\[\[(.+)\]\[(.+)\]\]/ ) {
213
# use common defining topics with different field titles
214
$definingTopic = fieldTitle2FieldName($1);
218
my $name = fieldTitle2FieldName($title);
220
# Rename fields with reserved names
221
if ( $reservedFieldNames{$name} ) {
225
my $fieldDef = $this->createField(
232
attributes => $attributes,
233
definingTopic => $definingTopic,
235
topic => $this->{topic}
237
push( @fields, $fieldDef );
239
$this->{mandatoryFieldsPresent} ||= $fieldDef->isMandatory();
250
# Create a field object. Done like this so that this method can be
251
# overridden by subclasses to extend the range of field types.
256
# The untaint is required for the validation *and* the ucfirst, which
257
# retaints when use locale is in force
258
my $class = Foswiki::Sandbox::untaint(
262
$class =~ /^(\w*)/; # cut off +buttons etc
263
return 'Foswiki::Form::' . ucfirst($1);
267
eval 'require ' . $class;
270
# Type not available; use base type
271
require Foswiki::Form::FieldDefinition;
272
$class = 'Foswiki::Form::FieldDefinition';
274
return $class->new( session => $this->{session}, type => $type, @_ );
277
# Generate a link to the given topic, so we can bring up details in a
280
my ( $this, $meta, $string, $tooltip, $topic ) = @_;
282
$string =~ s/[\[\]]//go;
286
$this->{session}->i18n->maketext('Details in separate window');
287
$tooltip ||= $defaultToolTip;
291
$this->{session}->normalizeWebTopicName( $this->{web}, $topic );
295
my $store = $this->{session}->{store};
296
if ( $store->topicExists( $web, $topic ) ) {
302
$this->{session}->getScriptUrl( 0, 'view', $web, $topic ),
310
$this->{session}->handleCommonTags( $string, $web, $topic, $meta );
311
if ( $tooltip ne $defaultToolTip ) {
312
$link = CGI::span( { title => $tooltip }, $expanded );
324
---++ ObjectMethod renderForEdit( $web, $topic, $meta ) -> $html
326
* =$web= the web of the topic being rendered
327
* =$topic= the topic being rendered
328
* =$meta= the meta data for the form
330
Render the form fields for entry during an edit session, using data values
336
my ( $this, $web, $topic, $meta ) = @_;
337
ASSERT( $meta->isa('Foswiki::Meta') ) if DEBUG;
339
my $session = $this->{session};
341
if ( $this->{mandatoryFieldsPresent} ) {
342
$session->enterContext('mandatoryfields');
344
my $tmpl = $session->templates->readTemplate("form");
345
$tmpl = $session->handleCommonTags( $tmpl, $web, $topic, $meta );
347
# Note: if WEBFORMS preference is not set, can only delete form.
348
$tmpl =~ s/%FORMTITLE%/_link(
349
$this, $meta, $this->{web}.'.'.$this->{topic})/ge;
350
my ( $text, $repeatTitledText, $repeatUntitledText, $afterText ) =
351
split( /%REPEAT%/, $tmpl );
353
foreach my $fieldDef ( @{ $this->{fields} } ) {
356
my $tooltip = $fieldDef->{tooltip};
357
my $definingTopic = $fieldDef->{definingTopic};
358
my $title = $fieldDef->{title};
360
if ( !$title && !$fieldDef->isEditable() ) {
362
# Special handling for untitled labels.
363
# SMELL: Assumes that uneditable fields are not multi-valued
364
$tmp = $repeatUntitledText;
365
$value = $session->{renderer}->getRenderedVersion(
366
$session->handleCommonTags(
373
$tmp = $repeatTitledText;
375
if ( defined( $fieldDef->{name} ) ) {
376
my $field = $meta->get( 'FIELD', $fieldDef->{name} );
377
$value = $field->{value};
379
my $extra = ''; # extras on col 0
381
unless ( defined($value) ) {
382
my $dv = $fieldDef->getDefaultValue($value);
383
if ( defined($dv) ) {
386
->handleCommonTags( $dv, $web, $topic, $meta );
387
$value = Foswiki::expandStandardEscapes($dv); # Item2837
391
# Give plugin field types a chance first (but no chance to add to
393
# SMELL: assumes that the field value is a string
394
my $output = $session->{plugins}->dispatch(
395
'renderFormFieldForEditHandler', $fieldDef->{name},
396
$fieldDef->{type}, $fieldDef->{size},
397
$value, $fieldDef->{attributes},
406
$fieldDef->renderForEdit( $web, $topic, $value );
409
if ( $fieldDef->isMandatory() ) {
410
$extra .= CGI::span( { class => 'foswikiAlert' }, ' *' );
413
$tmp =~ s/%ROWTITLE%/_link(
414
$this, $meta, $title, $tooltip, $definingTopic )/ge;
415
$tmp =~ s/%ROWEXTRA%/$extra/g;
417
$tmp =~ s/%ROWVALUE%/$value/g;
427
---++ ObjectMethod renderHidden( $meta ) -> $html
429
Render form fields found in the meta as hidden inputs, so they pass
430
through edits untouched.
435
my ( $this, $meta ) = @_;
436
ASSERT( $meta->isa('Foswiki::Meta') ) if DEBUG;
440
foreach my $field ( @{ $this->{fields} } ) {
441
$text .= $field->renderHidden($meta);
449
---++ ObjectMethod getFieldValuesFromQuery($query, $metaObject) -> ( $seen, \@missing )
451
Extract new values for form fields from a query.
453
* =$query= - the query
454
* =$metaObject= - the meta object that is storing the form values
456
For each field, if there is a value in the query, use it.
457
Otherwise if there is already entry for the field in the meta, keep it.
459
Returns the number of fields which had values provided by the query,
460
and a references to an array of the names of mandatory fields that were
461
missing from the query.
465
sub getFieldValuesFromQuery {
466
my ( $this, $query, $meta ) = @_;
467
ASSERT( $meta->isa('Foswiki::Meta') ) if DEBUG;
471
# Remove the old defs so we apply the
472
# order in the form definition, and not the
473
# order in the previous meta object. See Item1982.
474
my @old = $meta->find('FIELD');
475
$meta->remove('FIELD');
476
foreach my $fieldDef ( @{ $this->{fields} } ) {
477
my ( $set, $present ) =
478
$fieldDef->populateMetaFromQueryData( $query, $meta, \@old );
482
if ( !$set && $fieldDef->isMandatory() ) {
484
# Remember missing mandatory fields
485
push( @missing, $fieldDef->{title} || "unnamed field" );
488
return ( $seen, \@missing );
493
---++ ObjectMethod isTextMergeable( $name ) -> $boolean
495
* =$name= - name of a form field (value of the =name= attribute)
497
Returns true if the type of the named field allows it to be text-merged.
499
If the form does not define the field, it is assumed to be mergeable.
503
sub isTextMergeable {
504
my ( $this, $name ) = @_;
506
my $fieldDef = $this->getField($name);
508
return $fieldDef->isTextMergeable();
511
# Field not found - assume it is mergeable
517
---++ ObjectMethod getField( $name ) -> $fieldDefinition
519
* =$name= - name of a form field (value of the =name= attribute)
521
Returns a =Foswiki::Form::FieldDefinition=, or undef if the form does not
527
my ( $this, $name ) = @_;
528
foreach my $fieldDef ( @{ $this->{fields} } ) {
529
return $fieldDef if ( $fieldDef->{name} && $fieldDef->{name} eq $name );
536
---++ ObjectMethod getFields() -> \@fields
538
Return a list containing references to field name/value pairs.
539
Each entry in the list has a {name} field and a {value} field. It may
540
have other fields as well, which caller should ignore. The
541
returned list should be treated as *read only* (must not be written to).
547
return $this->{fields};
550
sub renderForDisplay {
551
my ( $this, $meta ) = @_;
553
my $templates = $this->{session}->templates;
554
$templates->readTemplate('formtables');
557
my $rowTemplate = $templates->expandTemplate('FORM:display:row');
558
foreach my $fieldDef ( @{ $this->{fields} } ) {
559
my $fm = $meta->get( 'FIELD', $fieldDef->{name} );
561
my $fa = $fm->{attributes} || '';
562
unless ( $fa =~ /H/ ) {
563
my $row = $rowTemplate;
565
# Legacy; was %A_TITLE% before it was $title
566
$row =~ s/%A_TITLE%/\$title/g;
567
$row =~ s/%A_VALUE%/\$value/g; # Legacy
568
$text .= $fieldDef->renderForDisplay( $row, $fm->{value} );
571
$text = $templates->expandTemplate('FORM:display:header') . $text;
572
$text .= $templates->expandTemplate('FORM:display:footer');
574
# substitute remaining placeholders in footer and header
575
$text =~ s/%A_TITLE%/$this->{web}.$this->{topic}/g;
580
# extractPseudoFieldDefs( $meta ) -> $fieldDefs
581
# Examine the FIELDs in $meta and reverse-engineer a set of field
582
# definitions that can be used to construct a new "pseudo-form". This
583
# fake form can be used to support editing of topics that have an attached
584
# form that has no definition topic.
585
sub _extractPseudoFieldDefs {
586
my ( $this, $meta ) = @_;
587
my @fields = $meta->find('FIELD');
589
require Foswiki::Form::FieldDefinition;
590
foreach my $field (@fields) {
592
# Fields are name, value, title, but there is no other type
593
# information so we have to treat them all as "text" :-(
594
my $fieldDef = new Foswiki::Form::FieldDefinition(
595
session => $this->{session},
596
name => $field->{name},
597
title => $field->{title} || $field->{name},
598
attributes => $field->{attributes} || ''
600
push( @fieldDefs, $fieldDef );
609
Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/, http://Foswiki.org/
611
Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors
612
are listed in the AUTHORS file in the root of this distribution.
613
NOTE: Please extend that file, not this notice.
615
Additional copyrights apply to some or all of the code in this
618
Copyright (C) 2001-2007 Peter Thoeny, peter@thoeny.org
619
and TWiki Contributors. All Rights Reserved. TWiki Contributors
620
are listed in the AUTHORS file in the root of this distribution.
621
NOTE: Please extend that file, not this notice.
623
This program is free software; you can redistribute it and/or
624
modify it under the terms of the GNU General Public License
625
as published by the Free Software Foundation; either version 2
626
of the License, or (at your option) any later version. For
627
more details read LICENSE in the root of this distribution.
629
This program is distributed in the hope that it will be useful,
630
but WITHOUT ANY WARRANTY; without even the implied warranty of
631
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
633
As per the GPL, removal of this notice is prohibited.