# Module of TWiki Enterprise Collaboration Platform, http://TWiki.org/ # # Copyright (C) 2001-2018 Peter Thoeny, peter[at]thoeny.org # and TWiki Contributors. All Rights Reserved. TWiki Contributors # are listed in the AUTHORS file in the root of this distribution. # NOTE: Please extend that file, not this notice. # # Additional copyrights apply to some or all of the code in this # file as follows: # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 3 # of the License, or (at your option) any later version. For # more details read LICENSE in the root of this distribution. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # As per the GPL, removal of this notice is prohibited. # # base class for all form field types =pod ---+ package TWiki::Form::FieldDefinition Base class of all field definition classes. Type-specific classes are derived from this class to define specific per-type behaviours. This class also provides default behaviours for when a specific type cannot be loaded. =cut package TWiki::Form::FieldDefinition; use strict; use Assert; =pod ---++ ClassMethod new(%...) Construct a new FieldDefinition. Parameters are passed in a hash. See Form.pm for how it is called. Subclasses should pass @_ on to this class. =cut sub new { my $class = shift; my %attrs = @_; ASSERT($attrs{session}) if DEBUG; $attrs{name} ||= ''; $attrs{attributes} ||= ''; $attrs{parameters} ||= ''; $attrs{type} ||= ''; # default $attrs{size} =~ s/\s+//g; return bless(\%attrs, $class); } =begin twiki ---++ ObjectMethod finish() Break circular references. =cut # Note to developers; please undef *all* fields in the object explicitly, # whether they are references or not. That way this method is "golden # documentation" of the live fields in the object. sub finish { my $this = shift; undef $this->{name}; undef $this->{attributes}; undef $this->{parameters}; undef $this->{type}; undef $this->{size}; undef $this->{session}; } =pod ---++ isEditable() -> $boolean Is the field type editable? Labels aren't, for example. Subclasses may need to redefine this. =cut sub isEditable { 1 } =pod ---++ isMultiValued() -> $boolean Is the field type multi-valued (i.e. does it store multiple values)? Subclasses may need to redefine this. =cut sub isMultiValued { 0 } =pod ---++ isTextMergeable() -> $boolean Is this field type mergeable using a conventional text merge? =cut # can't merge multi-valued fields (select+multi, checkbox) sub isTextMergeable { return !shift->isMultiValued() } =pod ---++ isMandatory() -> $boolean Is this field mandatory (required)? =cut sub isMandatory { my $parameters = shift->{parameters}; return ( $parameters && $parameters->{mandatory} ) ? 1 : 0; } =pod ---++ renderForEdit( $web, $topic, $value ) -> ($col0html, $col1html) =$web= - the web containing the topic being edited =$topic= - the topic being edited Render the field for editing. Returns two chunks of HTML; the =$col0html= is appended to the HTML for the first column in the form table, and the =$col1html= is used as the content of the second column. =cut sub renderForEdit { my( $this, $web, $topic, $value ) = @_; # Treat like text, make it reasonably long, add a warning return ( '
MISSING TYPE '. $this->{type}.'', CGI::textfield( -class => $this->cssClasses('twikiEditFormError'), -name => $this->{name}, -size => 80, -value => $value )); } =pod ---++ cssClasses(@classes) -> $classes Construct a list of the CSS classes for the form field. Adds additional class specifiers related to the attributes of the field e.g mandatory. Pass it a list of the other classnames you want on the field. =cut sub cssClasses { my $this = shift; if ($this->isMandatory()) { push(@_, 'twikiMandatory'); } return join(' ', @_); } =pod ---++ getDefaultValue() -> $value Try and get a sensible default value for the field from the values stored in the form definition. The result should be a value string. Some subclasses may not support the definition of defaults in the form definition. In that case this method should return =undef=. =cut sub getDefaultValue { my $this = shift; my $value = $this->{value}; $value = '' unless defined $value; # allow 0 values return $value; } =pod ---++ renderHidden($meta) -> $html Render the form in =$meta= as a set of hidden fields. =cut sub renderHidden { my( $this, $meta ) = @_; my $value; if( $this->{name} ) { my $field = $meta->get( 'FIELD', $this->{name} ); $value = $field->{value}; } my @values; if( defined( $value )) { if( $this->isMultiValued() ) { push( @values, split(/\s*,\s*/, $value )); } else { push( @values, $value ); } } else { $value = $this->getDefaultValue(); push( @values, $this->getDefaultValue() ) if $value; } return '' unless scalar( @values ); return CGI::hidden( -name => $this->{name}, -default => \@values ); } =pod ---++ populateMetaDataFromQuery( $query, $meta, $old ) -> ($bValid, $bPresent) Given a CGI =$query=, a =$meta= object, and an array of =$old= field entries, then populate the $meta with a row for this field definition, taking the content from the query if it's there, otherwise from $old or failing that, from the default defined for the type. Refuses to update mandatory fields that have an empty value. Return $bValid true if the value in $meta was updated (either from the query or from a default in the form. Return $bPresent true if a value was present in the query (even it was undef) =cut sub populateMetaFromQueryData { my( $this, $query, $meta, $old ) = @_; my $value; my $bPresent = 0; return unless $this->{name}; if( defined( $query->param( $this->{name} ))) { $bPresent = 1; if( $this->isMultiValued() ) { my @values = $query->param( $this->{name} ); if( scalar( @values ) == 1 ) { @values = split( /,|%2C/, $values[0] ); } my %vset = (); foreach my $val ( @values ) { $val =~ s/^\s*//o; $val =~ s/\s*$//o; # skip empty values $vset{$val} = (defined $val && $val =~ /\S/); } $value = ''; my $isValues = ( $this->{type} =~ /\+values/ ); foreach my $option ( @{$this->getOptions()} ) { $option =~ s/^.*?[^\\]=(.*)$/$1/ if $isValues; # Maintain order of definition if( $vset{$option} ) { $value .= ', ' if length( $value ); $value .= $option; } } } else { $value = $query->param( $this->{name} ); if( defined( $value ) && $this->{session}->inContext('edit')) { $value = TWiki::expandStandardEscapes( $value ); } } } # Find the old value of this field my $preDef; foreach my $item ( @$old ) { if( $item->{name} eq $this->{name} ) { $preDef = $item; last; } } my $def; if( defined( $value ) ) { # mandatory fields must have length > 0 if( $this->isMandatory() && length( $value ) == 0) { return (0, $bPresent); } # NOTE: title and name are stored in the topic so that it can be # viewed without reading in the form definition my $title = $this->{title}; if( $this->{definingTopic} ) { $title = '[['.$this->{definingTopic}.']['.$title.']]'; } $def = { name => $this->{name}, title => $title, value => $value, }; } elsif( $preDef ) { $def = $preDef; } else { return (0, $bPresent); } $meta->putKeyed( 'FIELD', $def ) if $def; return (1, $bPresent); } =pod ---++ ObjectMethod renderForDisplay($format, $value, $attrs) -> $html Render the field for display, under the control of $attrs. The following vars in $format are expanded: $title - title of the form field $value - expanded to the *protected* value of the form field The value is protected by TWiki::Render::protectFormFieldValue. =cut sub renderForDisplay { my( $this, $format, $value, $attrs ) = @_; ASSERT(!$attrs || ref($attrs) eq 'HASH') if DEBUG; unless( $attrs->{showhidden} ) { my $fp = $this->{parameters}; return '' if( $fp && $fp->{hidden} ); } require TWiki::Render; $value = TWiki::Render::protectFormFieldValue( $value, $attrs ); $format =~ s/\$title/$this->{title}/g; $format =~ s/\$name/$this->{name}/g; $format =~ s/\$attributes/$this->attributesAsText()/ge; $format =~ s/\$type/$this->{type}/g; $format =~ s/\$size/$this->{size}/g; my $definingTopic = $this->{definingTopic}|'FIELD'; $format =~ s/\$definingTopic/$definingTopic/g; # Item7169: $value moved to bottom to avoid expansion of other tokens $format =~ s/\$value/$value/g; return $format; } =pod ---++ ObjectMethod attributesAsText() -> $text Returns field attribute parameters as text, format: key1="value1" key2=value2" =cut sub attributesAsText { my( $this ) = @_; my $fp = $this->{parameters}; return '' unless $fp; my $key; my @ss; foreach $key ( sort keys %$fp ) { unless( $key =~ /^(_ERROR|_RAW)$/ ) { my $es = ( $key eq '_DEFAULT' ) ? '' : $key.'='; my $val = $fp->{$key}; $val =~ s/"/\\"/g; push( @ss, $es.'"'.$val.'"' ); } } return join( ' ', @ss ); } 1;