#!/usr/bin/perl


## W3C W3C::Registration::ShowForm - W3C PERL Library

#Copyright Massachusetts Institute of technology, 1998.
#Written by Eric Prud'hommeaux
#Last revision, $Date: 2003/02/07 23:28:45 $

require 5.000;
use strict;
use Carp;
use CGI;
use Date::Manip;

$W3C::Registration::ShowForm::revision = '$Id: ShowForm.pm,v 1.51 2003/02/07 23:28:45 eric Exp $ ';
$W3C::Registration::ShowForm::VERSION = 0.14;

package W3C::Registration::MarkupFixerUpper;
@W3C::Registration::MarkupFixerUpper::ISA = ('W3C::Registration::FastHTMLParser');

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my ($default) = (shift);
    my $self = $class->SUPER::new(@_);
    bless ($self, $class);
    $self->{DEFAULT} = $default;
    $self->{TEXT} = '';
    $self->{REPLACED} = [];
#    $self->{STATE_STACK} = [$STATE_FRESH];
    $self->{ITZA_TEXTAREA} = 0;
    return $self;
}

sub start
{
    my($self, $tag, $attr, $attrseq, $origtext) = @_;
    if (lc $tag eq 'input' || lc $tag eq 'option') {
	$self->{TEXT} .= '<'.$tag;
	foreach my $key (keys %$attr) {
	    if (lc $key eq 'value') {
		if (lc $tag eq 'input') {
		    if ($attr->{'type'} eq 'text') {
			$self->{TEXT} .= ' value="'.$self->{DEFAULT}.'"';
		    } else {
			$self->{TEXT} .= ' checked' if (lc $attr->{$key} eq lc $self->{DEFAULT});
			$self->{TEXT} .= ' value="'.$attr->{$key}.'"';
		    }
		} else { # (lc $tag eq 'option')
		    $self->{TEXT} .= ' selected' if (lc $attr->{$key} eq lc $self->{DEFAULT});
		    $self->{TEXT} .= ' value="'.$attr->{$key}.'"';
		}
	    } elsif (lc $key ne 'selected' && lc $key ne 'checked') {
		$self->{TEXT} .= ' '.$key.'="'.$attr->{$key}.'"';
	    }
	}
	# input typ='text' may not have a value field at all so
	if (!$attr->{'value'}) {
	    if ($attr->{'type'} eq 'text') {
		$self->{TEXT} .= ' value="'.$self->{DEFAULT}.'"';
	    } elsif ($attr->{'type'} eq 'checkbox') {
		if ($self->{DEFAULT} eq 'on') {
		    $self->{TEXT} .= ' checked' ;
		}
	    }
	}
	$self->{TEXT} .= '>';
    } elsif (lc $tag eq 'textarea') {
	$self->{ITZA_TEXTAREA} = 1;
	$self->{TEXT} .= $origtext.$self->{DEFAULT};
    } else {
	$self->{TEXT} .= $origtext;
    }
}

sub end
{
    my($self, $tag, $origtext) = @_;
    $self->{TEXT} .= $origtext;
}

sub text {
    my($self, $text) = @_;
    if ($self->{ITZA_TEXTAREA}) {
	$self->{TEXT} .= $self->{DEFAULT};
	$self->{ITZA_TEXTAREA} = 0;
    } else {
	$self->{TEXT} .= $text;
    }
}

sub getText {
    my ($self) = @_;
    return $self->{TEXT};
}

package W3C::Registration::ShowForm;

#####
# per-class data

my $Debugging = 0;	# whether to show debugging stuff

#####
# per-object data
# - header info
# NAME		- name of a form
# DESCRIP	- description for form headers
# START		- start date
# ENDDATE	- end date
# CUTOFF	- last date for normal registration
# BLURB		- longer description for form bodies
# TAIL		- closing markup
# DEBUG		- per-object control of debugging info
# - specific field info
# FIELDNAMES	- input name (avoid leading _ as it is used internally)
# FIELDTYPES	- type of input
# FIELDVALUES	- default value
# FIELDSIZES	- max input length
# FIELDMARKUP	- html coming before this field

#####
# new - prepare a W3C::Registration::ShowForm with a new connection

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self  = {};

    # - header info
    $self->{QUERY}	= shift;
    $self->{REGISTRATION}= shift;

    $self->{DEBUG}	= 0;
    bless ($self, $class);
    $self->clearForm;
    return $self;
}

sub clearForm {
    my $self = shift;

    # form description
    $self->{ID}		= undef;
    $self->{NAME}	= undef;
    $self->{DESCRIP}	= undef;
    $self->{START}	= undef;
    $self->{ENDDATE}	= undef;
    $self->{CUTOFF}	= undef;
    $self->{BLURB}	= undef;
    $self->{TAIL}	= undef;
    $self->{END_BLURB}	= undef;
    $self->{CC}		= undef;
    $self->{CONTACT}	= undef;
    $self->{REPLYTO}	= undef;
    $self->{TYPE}	= undef;

    # field variables
    $self->{FIELDNAMES}	= [];
    $self->{FIELDTYPES}	= {};
    $self->{REQUIRED}	= {};
    $self->{FIELDMARKUP}= {};
    $self->{FIELDSEQNO}= {};
    $self->{FIELDINPUT}= {};

    # for generating table for parsed form
    $self->{FIELDDESC}= {};

    # and keeping track of edits
    $self->{CHANGED} = {};
    $self->{CGI} = undef;
    $self->{DB} = undef;
    $self->{ERRORS} = [];
}

#####
# insureCGI - load CGI hash with values from {QUERY} unless already loaded

sub insureCGI {
    my $self = shift;
    $self->{CGI} = $self->{REGISTRATION}->getCGIFieldData($self->{QUERY}, $self->{FIELDNAMES}) if (!defined $self->{CGI});
    return $self->{CGI};
}

#####
# insureDB - load DB hash with values from {REGISTRATION} unless already loaded

sub insureDB {
    my ($self, $dataId) = @_;
    $self->{DB} = $self->{REGISTRATION}->getDBFieldData($self->{SAVENAMES}, $self->{NAME}, $dataId) if (!defined $self->{DB});
    return $self->{DB};
}

sub foundDefaults {
    my ($self) = @_;
    return scalar %{$self->{DB}};
}

#####
# load - read in a form from a REGISTRATION

sub load {
    my $self = shift;
    my $id = shift;
    $self->{REGISTRATION}->getTableInfo($id, [\$self->{ID}, \$self->{NAME}, \$self->{DESCRIP}, \$self->{START}, 
					      \$self->{ENDDATE}, \$self->{CUTOFF}, \$self->{BLURB}, \$self->{TAIL}, 
					      \$self->{END_BLURB}, \$self->{CC}, \$self->{CONTACT}, \$self->{REPLYTO}, 
					      \$self->{TYPE}], 
					[$self->{FIELDNAMES}, $self->{FIELDTYPES}, $self->{REQUIRED}, $self->{FIELDMARKUP}, 
					 $self->{FIELDSEQNO}, $self->{FIELDINPUT}]);
    $self->{SAVENAMES} = $self->{REGISTRATION}->removeDummy($self->{FIELDNAMES}, $self->{FIELDTYPES});
}

#####
# parse - read in a form and pull out the input fields

sub parse {
    my $self = shift;
    my $form = shift;
    my $tag=0;
    my $soFar = '';
    my $seqNo = 0;
    my $nextMarkup = '';
    my %radioNames; # make sure that <intput type="radio" name="bob"> and <... name="BOB" ...> are the same field
    my %radioSizes; # max widht of <... value="12345" ...>
  PARSER: {
      my $start = pos $form;
      $form =~ m/ \G (.*?) <(input|select|textarea) \s+ /gcxsi && do {
	  $nextMarkup .= $1;
	  my $tag = $2;
	  $start += length($1); # backup past <input
	  my %attribs = $self->getTagAttributes(\$form);
	  my $name = $attribs{'name'};
	  if ($tag =~ m/input/i) {
	      my $len;
	      if ($attribs{'type'} =~ m/radio/i) {
		  my $baseName = $radioNames{lc $name}; # get real (case-sensitive) name from hash value
		  $len = length($attribs{'value'});
		  if (defined $baseName) {
		      if ($len > $radioSizes{$baseName}) { # get maximum width of all radio input values
			  $radioSizes{$baseName} = $len;
			  $self->{FIELDDESC}{$baseName} = 'varchar('.$len.') binary';
		      }
		      $nextMarkup .= substr($form, $start, pos($form) - $start); # pile into next field's markup
		      goto PARSER;
	  # --- --- don't assume $name is for current field above here --- ---
		  }
		  $radioNames{lc $name} = $name; # store real case (not lower) in hash value
		  $radioSizes{$name} = $len;
		  $self->{FIELDTYPES}{$name} = 'radio';
	      } else {
		  $len = $attribs{'maxlength'};
		  $len = 255 if (!defined $len);
		  $self->{FIELDTYPES}{$name} = 'text';
	      }
	      $self->{FIELDDESC}{$name} = 'varchar('.$len.') binary';
	  } elsif ($tag =~ m/select/i) {
	      $self->{FIELDTYPES}{$name} = 'select';
	      my $maxlength = 0;
	      while ($form =~ m/ \G <option \s+ /gcxsi) {
		  my %optionAttribs = $self->getTagAttributes(\$form);
		  my $len = length($optionAttribs{'value'});
		  $maxlength = $len if ($len > $maxlength);
		  $form =~ m/ \G (.*?) <\s*\/option\s*>\s* /gcxs || $form =~ m/ \G (.*?) (?=<) /gcxs;
	      }
	      &error('select', \$form) if ($form !~ m/ \G <\s*\/select\s*> \s* /gcxsi);
	      $self->{FIELDDESC}{$name} = 'varchar('.$maxlength.') binary';
	  } elsif ($tag =~ m/textarea/i) {
	      $self->{FIELDDESC}{$name} = 'text';
	      $self->{FIELDTYPES}{$name} = 'text';
	      &error('textarea', \$form) if ($form !~ m/ \G (.*?) <\s*\/textarea\s*> \s* /gcxsi);
	  }
	  push(@ {$self->{FIELDNAMES}}, $name);
	  $self->{FIELDMARKUP}{$name} = $nextMarkup;
	  $nextMarkup = '';
	  $self->{FIELDINPUT}{$name} = substr($form, $start, pos($form) - $start);
	  $seqNo++;
	  redo;
      };
      $self->{TAIL} = $nextMarkup . substr($form, pos $form);
      $self->{FIELDCOUNT} = $seqNo;
  }
}

sub getTagAttributes {
    my $self = shift;
    my $pForm = shift;
    my %attrs;
    while ($$pForm !~ m/\G >\s* /gcxs) {
	$$pForm =~ m/\G (\w*) \s* (=?) /gcxs || &error('html', $pForm);
	my $attr = lc($1);
	if ($2 eq '=') {
	    my $value;
	    if	    ($$pForm =~ m/ \G     \s*  \'    /gcxs) {
		$$pForm =~      m/ \G ([^\']*) \'\s* /gcxs || &error('html', $pForm);
		$value = $1;
	    } elsif ($$pForm =~ m/ \G     \s*  \"    /gcxs) {
		$$pForm =~      m/ \G ([^\"]*) \"\s* /gcxs || &error('html', $pForm);
		$value = $1;
	    } else {
		$$pForm =~      m/ \G  (\w*)\b   \s* /gcxs || &error('html', $pForm);
		$value = $1;
	    }
	    $attrs{$attr} = $value;
	} else {
	    $attrs{$attr} = undef;
	}
    }
    return %attrs;
}

#####
# error - handle parsing errors

sub error {
    my $what = shift;
    my $source = shift;
    die "unable to parse $what: \"",substr($$source, pos $$source, 50), "\"\n";
}

#####
# save a (parsed) form to the DB

sub saveForm {
    my ($self, $id, $tableName, $replace) = @_;
    my $tableDesc='id int unsigned PRIMARY KEY,';
    my $W3CRegistration = $self->{REGISTRATION};

    # add formFields entry for this field
    if (!$replace) {
	$W3CRegistration->deleteFormFields($id);
    }
    my $seqNo = 0;
    foreach my $name (@ {$self->{FIELDNAMES}}) {
	$seqNo++;
	if ($replace) {
	    $W3CRegistration->updateFormField($id, $self->{FIELDTYPES}{$name}, 
					      $self->{FIELDMARKUP}{$name},
					      $name, 
					      $self->{FIELDINPUT}{$name});
	} else {
	    $W3CRegistration->addFormField($id, $seqNo, $self->{FIELDTYPES}{$name}, 
					   $self->{FIELDMARKUP}{$name},
					   $name, 
					   $self->{FIELDINPUT}{$name}, 1);
	}
	$tableDesc .= ' '.$name.' '.$self->{FIELDDESC}{$name};
	#($self->{REQUIRED}[$i] && ($tableDesc .= ' NOT NULL'));
	$tableDesc .= ',';
    }
    # add last entry for tail of form
    if ($replace) {
	$W3CRegistration->updateFormField($id, $W3C::Registration::Registration::DUMMY, $self->{TAIL}, undef, undef);
    } else {
	$W3CRegistration->addFormField($id, $self->{FIELDCOUNT}+1, $W3C::Registration::Registration::DUMMY, $self->{TAIL}, undef, undef, 0);
    }

    # create table if it doesn't already exist
    if (!$W3CRegistration->tableExists($tableName)) {
	chop $tableDesc;
	$W3CRegistration->createTable($tableName,$tableDesc);
    }
}

#####
# showForm
#
# PARAMETERS: - see showMarkup

sub showForm {
    my ($self, $targetName) = (shift, shift);
    my $ret;
    
    $ret .= "<form method=\"post\" action=\"$targetName\" name=\"bigForm\" enctype=\"application/x-www-form-urlencoded\">\n";
    $ret .= $self->showMarkup(@_);
    $ret .= "\n<br>*: required field<hr>\n";
    $ret .= '<INPUT TYPE="submit" NAME="submit" VALUE="Submit"><INPUT TYPE="reset" VALUE="Clear Form">', "\n";
#    $ret .= $self->{QUERY}->submit(-name=>'submit',-value=>'Ok');
#    $ret .= $self->{QUERY}->reset(-value='reset it');
#    $ret .= $self->{QUERY}->reset;
    return $ret;
}

#####
# showMarkup
#
# PARAMETERS: 
# 1 $valuesFromCGI - get input values from CGI ($query) rather than database ($W3C::Registration::Registration)
# OPTIONAL PARAMETERS - providing these parms makes showMarkup set the input
#			defaults to match those in $dataTable where id=$dataId.
# 2 $dataId - id filter for $dataTable

sub showMarkup{
    my $self = shift;
    my $valuesFromCGI = shift;
    my $dataId = ref $_[0] ? undef : shift; # pick up defaults from table where id=$dataId
    my $filter = shift;

    my $ret;
    my $query = $self->{QUERY};
#    my $W3CRegistration = $self->{REGISTRATION};
    
    my $defaults = $valuesFromCGI ? $self->insureCGI : 
	defined $dataId ? $self->insureDB($dataId) : 
	{};
    my @uncheckedRadios; # see UNCHECKED RADIO BUTTONS below
    foreach my $name (@ {$self->{FIELDNAMES}}) {
	$ret .= $self->{FIELDMARKUP}{$name};
	if ($self->{FIELDTYPES}{$name} ne '_w3c_dummy') {
	    my $renderValue = $self->renderFieldData($defaults->{$name}, $name, 1, $dataId);
	    my $defaulted = $self->renderFieldMarkup($renderValue,$name,$self->{FIELDINPUT}{$name});
	    $ret .= $defaulted;
	    if ($self->{FIELDTYPES}{$name} eq 'radio' && 
		$defaults->{$name} && 
		$defaulted !~ /\bchecked\b/) {
		push (@uncheckedRadios, $name); # see UNCHECKED RADIO BUTTONS below.
	    }
	    my $req = $self->{REQUIRED}{$name} eq 'Y';
	    $ret .= eval {&$filter($name, $req)} if (defined $filter);
	}
    }
    # UNCHECKED RADIO BUTTONS:

    # This markup is divided into records fields for each named input. However,
    # radio buttions have <n> inputs for a radio button set with a given
    # name. Only the first radio input markup is stored in the input field in
    # the database. Therefor, if the type is a radio, and it is unchecked, we
    # (probably) have another we have another input field burried in the markup
    # and that input field may have the value that matches
    # $defaults->{$name}. This hack records the unchecked radio buttons (those
    # whose default did not match the first input field) and looks in the
    # rendered output for the input field with the matching name and default
    # (value).

    foreach my $name (@uncheckedRadios) {
	my $default = $defaults->{$name};
	$ret =~ s/ (<input .*? )
	    ( (?: name=\"$name\" .*? value=\"$default\") | 
	      (?: value=\"$default .*? name=\"$name\") )
	    (.*? >)/$1 $2 checked $3/x;
    }

    return $ret;
}

#####
# renderFieldData - display field data in format determined by FIELDTYPES
# 
# example 1: bob@hq.lcs.mit.edu
# example 2: nov_18th

sub renderFieldData {
    my $self = shift;
    my $value = shift;
    my $name = shift;
    my $verbose = shift;
    my $sourceMeetingId = shift;

    if ($self->{FIELDTYPES}{$name} eq '_w3c_formSource') {
	if ($verbose) {
	    my $subSource = new W3C::Registration::ShowForm($self->{QUERY}, $self->{REGISTRATION});
	    $subSource->load($sourceMeetingId);
	    return $self->{QUERY}->escapeHTML($subSource->showMarkup(0, $sourceMeetingId));
	} else {
	    return 'form source ommited';
	}
    }
    if ($self->{FIELDTYPES}{$name} eq '_w3c_date') {
	return $self->{REGISTRATION}->dateToStr($value) if (defined $value);
    }
    return $value;
}

#####
# renderFieldMarkup - add default values to and display markup for an input field
# 
# example 1: <input type="text" name="email" maxlength=255 value="bob@hq.lcs.mit.edu">
# example 2: <select name="attendance" value size="1">
#	      <option value>None </option>
#	      <option value="nov_18th_and_19th">both the 18th and the 19th </option>
#	      <option value="nov_18th" selected>only November 18th </option>
#	      <option value="nov_19th">only November 19th </option>
#	     </select>

sub renderFieldMarkup {
    my $self = shift;
    my $default = shift;
    my $name = shift;
    local $_ = shift;

#    ($default eq '' && return $_."no default<br>\n");
    if ($self->{FIELDTYPES}{$name} =~ m/\A_w3c_list_(.*)\Z/ && 
	$self->arrayTextIncludes($1, '_', $self->{QUERY}->param('_w3c_nextMeetingId') ? 
				 $self->{QUERY}->param('_w3c_nextMeetingId') : 
				 $self->{REGISTRATION}->getMeetingId($self->{QUERY}->param('_w3c_nextMeetingName'))) > 0) {
	$_ = $self->renderTheList($name);
    }
    (!$default && return $_);
    my $markupFixerUpper = new W3C::Registration::MarkupFixerUpper($default);
    $markupFixerUpper->parse($_);
    return $markupFixerUpper->getText;

    # NUKE
    if (/<input.*type\s*=\s*[\'\"]?text[\'\"]?.*>/i) {
	s/<input(.*)\s+value\s*=\s*[\'\"].*[\'\"]\s*(.*)>/<input $1 $2>/i; # take out the original default value
	s/<input\s+([^>]*)>/<input $1 value=\"$default\">/i; # put in our default
    } elsif (/<select.*>/i) {
	s/<option\s+(.*)\s+selected\s*(.*)>/<option $1 $2>/i; # take out the original selected
	s/<option\s+value=[\'\"]$default[\'\"]\s*>/<option value=\"$default\" selected>/i; #put in the new one
    } elsif (/<textarea.*>/i) {
	s/>[^<]*</>$default</i; # take out the original included text and put in the new one
    }
    return $_;
}

#####
# displayErrors - show all problems with validated values

sub displayErrors {
    my $self = shift;
    my $W3CRegistration = $self->{REGISTRATION};
    my $values = $self->{INPUTVALUE};
    my $ret;

    if (@{$self->{ERRORS}}) {
	$ret .= "There were some problems with the information you entered:\n<ul>\n";
	foreach my $error (@{$self->{ERRORS}}) {
	    $ret .= "<li>$error</li>";
	}
	$ret .= "</ul><i>Please correct your information:</i><hr>\n";
	return $ret;
    }
    return undef;
}

#####
# displayChanges - show all changed values

sub displayChanges {
    my $self = shift;
    my $markChanges = shift;
    my $ret;
    $ret .= "<ul>\n"; # changed values end up bold - woohoo
    foreach my $name (@ {$self->{SAVENAMES}}) {
	my $changed = $self->{CHANGED}{$name} && $markChanges;
	my $renderedData = $self->renderFieldData($self->{INPUTVALUE}{$name}, $name, 0);
	if (!$renderedData && !$changed) {
	    next;
	}
	my $line = '';
	$line .= "<li>$name: ";
	$line .= '<b>' if ($changed);
	$line .= $renderedData;
	$line .= '&nbsp;&nbsp;&nbsp;*CHANGED</b>' if ($changed);
	$line .= "</li>\n";
	$ret .= $line;
    }
    $ret .= "</ul><hr>\n";
}

sub arrayTextIncludes {
    my ($self, $text, $delim, $lookFor) = @_;
    my @array = split($delim, $text);
    my $ret = 0;
    map {$ret++ if ($_ eq $lookFor);} @array;
    return $ret;
}

sub renderTheList {
    my ($self, $name)= @_;
    my @orgs = $self->{REGISTRATION}->getACList;
    push(@orgs,'W3C');
    return $self->{QUERY}->scrolling_list(-name=>$name,-size=>'5',-values=>\@orgs);
}

#####
# saveFormData - check form data against current data and record if necessary
#
# PARAMETERS: 

sub checkFormData {
    my $self = shift;
    my $meetingId = shift;
    my $dataId = shift;

    my $query = $self->{QUERY};
#    my $W3CRegistration = $self->{REGISTRATION};

#    my (%seqNo, %input);
    # get defaults from DB
    my $defaults = $self->insureDB($dataId);
    my $ret = 0;
    my $hasFormSource = 0;

    # get input values from CGI
    $self->{INPUTVALUE} = $self->insureCGI;
    $self->validate($dataId);
    foreach my $name (@ {$self->{FIELDNAMES}}) {
	if ($self->{INPUTVALUE}{$name} eq $defaults->{$name}) {
	    $self->{CHANGED}{$name} = 0;
	} else {
	    $self->{CHANGED}{$name} = 1;
	    $ret++;
	}
    }
    return @{$self->{ERRORS}} ? -1 : $ret;
}

#####
# validate - check values in INPUTVALUE against input criteria stored in FIELDTYPES
#            keep track of changed values

sub validate {
    my $self = shift;
    my $W3CRegistration = $self->{REGISTRATION};
    my $dataId = shift;

    local $_ = $self->{INPUTVALUE}{'country'};
    my $inUS = (/^\s*USA\s*$/i || /^\s*US\s*$/i || /^\s*United\s+States\s*$/i ||
		/^\s*United\s+States\s+of\s+America\s*$/i);

    foreach my $name (@ {$self->{FIELDNAMES}}) {
	my $error = '';
	local $_ = $self->{INPUTVALUE}{$name};


	if ($self->{REQUIRED}{$name} eq 'Y' && !$_) {
	    $error .= "This field needs to be filled in. ";
	}
	if ($inUS && ($self->{FIELDTYPES}{$name} eq '_w3c_postalCode' || $self->{FIELDTYPES}{$name} eq '_w3c_state') && !$_) {
	    $error .= "If you reside in the United States, postal code and state have to be filled in. ";
	}	

	if ($_) {
	    if (($self->{FIELDTYPES}{$name} eq 'select' || $self->{FIELDTYPES}{$name} eq 'boolean') &&
		($self->{REQUIRED}{$name} eq 'Y') && /^-$/) {
		$error .= "Please choose a value for this field. ";
	    }
	    if ($self->{FIELDTYPES}{$name} eq '_w3c_postalCode' && $inUS && (!/^\d\d\d\d\d$/ && !/^\d\d\d\d\d-\d\d\d\d$/)) {
		$error .= "US postal codes are of the form XXXXX or XXXXX-XXXX. ";
	    }
	    my @misses = m/([^0-9x \(\)\-\+\.])/g;
	    if ($self->{FIELDTYPES}{$name} eq '_w3c_phone' && m/([^0-9x \(\)\-\+\.])/g) {
		$error .= '"'.join('',@misses).'" is invalid in a phone number. Use only numerical digits, (, ), -, +, ., and x. ';
	    }
	    if ($self->{FIELDTYPES}{$name} eq '_w3c_email') {
#		from Gerald's famed (!/[^A-Za-z0-9@\.+%#!_-]/)
		if (!/\A(\w|-|_|\+|\.|\%|\#|\!)+@(\w|-)+(\.(\w|-)+)+\Z/) {
		    $error .= "This is an invalid email address. ";
		} elsif ($self->emailExists($W3CRegistration,$_,$dataId)) {
		    $error .= "Another user in our database already has this email address. Please enter another one. ";
		}
	    }
	    if ($self->{FIELDTYPES}{$name} eq '_w3c_state' && $inUS && !/^A[KLRZ]$|^C[AOT]$|^D[CE]$|^FL$|^GA$|^HI$|^I[ADLN]$|^K[SY]$|^LA$|^M[ADEINOST]$|^N[BCDHJMVY]$|^O[HKR]$|^PA$|^RI$|^S[CD]$|^T[NX]$|^UT$|^V[AT]$|^W[AIVY]$/i) {
		$error .= "Please enter a valid 2-letter abbreviation for your state. ";
	    }
	    if ($self->{FIELDTYPES}{$name} eq '_w3c_date') {
		my $date = &Date::Manip::ParseDate($_);
		$self->{INPUTVALUE}{$name} = &Date::Manip::UnixDate($date, "%Y-%m-%d")
	    }
	    if ($self->{FIELDTYPES}{$name} =~ m/\A_w3c_memberList_(\d+)\Z/){
		
	    }
	    if ($self->{FIELDTYPES}{$name} eq $W3C::Registration::Registration::FORMSOURCE) {
		my $newForm = new W3C::Registration::ShowForm($self->{QUERY}, $self->{REGISTRATION});
		$newForm->parse($_);
		$self->{INPUTVALUE}{$name} = $newForm;
		$self->{TAIL} = $newForm->{TAIL};
	    }
	}
	($error && $self->addError("<b>$name: </b>$error"));
    }
    return;
}

sub addError {
    my ($self, $error) = @_;
    push (@{$self->{ERRORS}}, $error);
}

#####
# writeFormData - write data from INPUTVALUE into fields FIELDNAMES

sub writeFormData {
    my ($self, $meetingId, $dataId, $replace) = @_;

    my $query = $self->{QUERY}; # needed only for param('name') - put in structure
    my $W3CRegistration = $self->{REGISTRATION};

    my $insertString = $dataId; # first field is always the id

    foreach my $name (@ {$self->{FIELDNAMES}}) {
	if ($self->{FIELDTYPES}{$name} eq $W3C::Registration::Registration::FORMSOURCE) {
	    $self->{INPUTVALUE}{$name}->saveForm($dataId, $query->param('name'), $replace); # insert data into formFields
#	    push(@ {$self->{SAVENAMES}}, 'tail'); - now saved in _w3c_dummy field
#	    $insertString .= ', '.$W3CRegistration->escape($self->{TAIL});
	    push(@ {$self->{SAVENAMES}}, 'type');
	    $insertString .= ',\'M\'';
	} elsif ($self->{FIELDTYPES}{$name} ne '_w3c_dummy') {
	    $insertString .= ', '.$W3CRegistration->escapeWithNULL($self->{INPUTVALUE}{$name});
	}
    }

    unshift (@ {$self->{SAVENAMES}}, 'id');
    $W3CRegistration->setFieldData($self->{SAVENAMES}, $self->{NAME}, $insertString);
    shift (@ {$self->{SAVENAMES}});
}

#####
# addFieldPair - add an attribute,value pair to be stored in writeFormData

sub addFieldPair {
    my $self = shift;
    my $attr = shift;
    my $value = shift;
    push(@ {$self->{FIELDNAMES}}, $attr); # writeFormData will walk FIELDNAMES to build an
    $self->{INPUTVALUE}{$attr} = $value;   # insert string from the INPUTVALUES. It will then
    push(@ {$self->{SAVENAMES}}, $attr);  # save all the values in SAVENAMES
}

sub emailExists {
    my $self = shift;
    my $W3CRegistration = shift;
    my $email = shift;
    my $id = shift;

    my @userId = ();

    my $st = "select id from users where email='$email' and id<>'$id'";
    return $W3CRegistration->executeQuery(1,\@userId,$st);
}

#####
# debug - cannonical debugging stuff from
# http://www.perl.com/CPAN-local/doc/manual/html/pod/perltoot/Debuging_Methods.html

sub debug {
    my $self = shift;
#    confess "usage: thing->debug(level)"    unless @_ == 1;
    my $level = shift;
    if (ref($self))  {
	$self->{"_DEBUG"} = $level;
    } else {
	$Debugging = $level;            # whole class
    }
  $self->SUPER::debug($Debugging);
}

1;

