#!/usr/bin/perl
# ----------------------------------------------------------------------------
# This software is Copyright (C) 2007, All Rights Reserved
# ----------------------------------------------------------------------------
# This program is protected by local and international copyright laws. Any
# use of this program is subject to the the terms of the license agreement
# included as part of this distribution archive. Any other uses are strictly
# prohibited without the written permission of the Vendor and all
# other rights are reserved.
# -----------------------------------------------------------------------------
# Warning : Modifying this program violates your license agreement!
# -----------------------------------------------------------------------------

package IT::App::Menu::Admin::DatabaseEditor::FieldEditor;

use IT::Globals;
use IT::DB::Globals;

use strict;

use vars qw(%VALID_APP_FIELD_TYPES %FIELD_OPTION_FIELDS %VALIDATION_RULE_FIELDS %FIELD_SEPARATOR_FIELDS @ALL_FIELD_EDITOR_FIELDS $SEPARATOR_HTML_DEFAULT $CUSTOM_FIELD_HTML_DEFAULT);
%VALID_APP_FIELD_TYPES     = map { $_ => 1 } qw(none textfield textbox date list checkbox upload custom);
%FIELD_OPTION_FIELDS       = map { $_ => 1 } qw(textfield_defaultValue textfield_isPasswordField textbox_defaultValue textbox_heightInPixels textbox_isWysiwyg textbox_noAutoFormat date_showHourMinFields date_showSecondsField list_singleOrMultipleChoice list_singleType list_singleRows list_multipleType list_multipleRows checkbox_checkedByDefault checkbox_description checkbox_checkedValue checkbox_uncheckedValue upload_type upload_maxUploads useCustomFieldHtml customFieldHtml);
%VALIDATION_RULE_FIELDS    = map { $_ => 1 } qw(valueIsRequired valueMustBeUnique checkMinLength checkMaxLength minLength maxLength validationRuleType validationRule_validElementName validationRule_allowCharsLetter validationRule_allowCharsNumber validationRule_allowCharsOther validationRule_allowCharsOtherValue validationRule_disallowCharsLetter validationRule_disallowCharsNumber validationRule_disallowCharsOther validationRule_disallowCharsOtherValue validationRule_perlRegexp useCustomErrorForValidationRule customValidationRuleError uploadExtOverride uploadExtAllowed uploadFileSizeOverride uploadMaxFileSize uploadImageSizeOverride uploadMaxImageHeight uploadMaxImageWidth uploadThumbSizeOverride uploadMaxThumbHeight uploadMaxThumbWidth);
%FIELD_SEPARATOR_FIELDS    = map { $_ => 1 } qw(separatorType separatorHeaderText separatorHTML);
@ALL_FIELD_EDITOR_FIELDS   = (keys %FIELD_OPTION_FIELDS, keys %VALIDATION_RULE_FIELDS);
$SEPARATOR_HTML_DEFAULT    = ("<tr>\n <td>&nbsp;</td>\n <td>&nbsp;</td>\n</tr>\n");
$CUSTOM_FIELD_HTML_DEFAULT = qq| <tr>\n  <td style="padding-right:5px">\$_fieldLabel\$</td>\n  <td><input type="text" name="\$_fieldName:htmlEncoded\$" value="\$_fieldValue:htmlEncoded\$" size="40" /></td>\n </tr>\n|;

# ----------------------------------------------------------------------------
# Function    : addSeparator
# Description :
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::addSeparator();
# ----------------------------------------------------------------------------

sub addSeparator {
  return &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::editSeparator(@_);
}

# ----------------------------------------------------------------------------
# Function    : editSeparator
# Description : display field edit page.  Loads data from FORM input first
#               (for maintaining state when errors are displayed) then schema
#               data
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::editSeparator();
# ----------------------------------------------------------------------------

sub editSeparator {
  my $errorMessage = shift;
  if ($errorMessage) { &IT::App::alert($errorMessage); }

  my $tableName  = $FORM->{'tableName'} || die "no tablename specified!";
  my $fieldName  = $FORM->{'fieldName'};
  my $isNewField = !$FORM->{'fieldName'};
  my $template   = "admin/menus/admin/databaseFieldEditor.html";

  # error checking
  if (!$isNewField) {
    my $appFieldType = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldType');
    if ($appFieldType ne 'separator') { die "Fieldname '$fieldName' isn't a separator field!\n"; }
  }

  # load saved separator data
  my($appFieldLabel, $appHiddenField, $appFieldSeparator);
  if (!$isNewField) {
    $appFieldLabel           = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldLabel');
    $appHiddenField          = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appHiddenField');
    $appFieldSeparator       = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldSeparator');
  }

  # set placeholders (use form input first, then saved data (if any), then default data)
  my $pagePlaceholders;
  $pagePlaceholders->{'appFieldLabel'}       = &firstDefinedValue($FORM->{'appFieldLabel'},       $appFieldLabel,                              '');
  $pagePlaceholders->{'appHiddenField'}      = &firstDefinedValue($FORM->{'appHiddenField'},      $appHiddenField,                             '0');
  $pagePlaceholders->{'separatorType'}       = &firstDefinedValue($FORM->{'separatorType'},       $appFieldSeparator->{'separatorType'},       'blankLine');
  $pagePlaceholders->{'separatorHeaderText'} = &firstDefinedValue($FORM->{'separatorHeaderText'}, $appFieldSeparator->{'separatorHeaderText'}, '');
  $pagePlaceholders->{'separatorHTML'}       = &firstDefinedValue($FORM->{'separatorHTML'},       $appFieldSeparator->{'separatorHTML'},       $SEPARATOR_HTML_DEFAULT);

  # set global placeholders
  &IT::Template::setGlobalPlaceholder('menuGroup', 'databaseEditor');
  &IT::Template::setGlobalPlaceholder('tableName', $tableName);
  &IT::Template::setGlobalPlaceholder('fieldName', $fieldName);

  ### display page
  &IT::App::displayInterface({
    'template'           => $template,
    'templatecell'       => 'editSeparator',
    'selectedMenuOption' => 'admin',
    'menuGroup'          => 'databaseEditor',
    'placeholders'       => $pagePlaceholders,
  });
}

# ----------------------------------------------------------------------------
# Function    : saveSeparator
# Description :
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::saveSeparator();
# ----------------------------------------------------------------------------

sub saveSeparator {
  my $tableName  = $FORM->{'tableName'} || die "no tablename specified!";
  my $fieldName  = $FORM->{'fieldName'};
  my $template   = "admin/menus/admin/databaseTableEditor.html";
  my $isNewField = !$FORM->{'fieldName'};

  # Note: Allowing user to save perl regexp code in valation rules could allow for code injection
  &IT::App::demoModeAlertAndAction("Creating and updating fields are disabled in demo mode.", \&IT::App::Menu::Admin::DatabaseEditor::TableEditor::listTables);

  ### Create new field
  &IT::FileLock::getLock('exclusive');
  if ($isNewField) {
    $fieldName = &IT::DB::Schema::generateNewFieldname($tableName, '___separator', '___');
    &IT::DB::Schema::createField($tableName, $fieldName, 'none');
  }

  ### update schema
  if ($isNewField) {
    my $highestDisplayOrder = &_getHighestFieldOrder($tableName);
    my $fieldDisplayOrder   = $highestDisplayOrder + 10;
    &IT::DB::Schema::setFieldAttribute($tableName, $fieldName, 'appDisplayOrder', $fieldDisplayOrder);
  }
  &IT::DB::Schema::setFieldAttribute($tableName, $fieldName, 'appFieldLabel',     $FORM->{'appFieldLabel'});
  &IT::DB::Schema::setFieldAttribute($tableName, $fieldName, 'appFieldType',      'separator');
  &IT::DB::Schema::setFieldAttribute($tableName, $fieldName, 'appHiddenField',    $FORM->{'appHiddenField'});
  &IT::DB::Schema::setFieldAttribute($tableName, $fieldName, 'appUserEditable',   1);
  &IT::DB::Schema::setFieldAttribute($tableName, $fieldName, 'type',              'none');
  &IT::DB::Schema::setFieldAttribute($tableName, $fieldName, 'appFieldSeparator', {
    'separatorType'       => $FORM->{'separatorType'},
    'separatorHeaderText' => $FORM->{'separatorHeaderText'},
    'separatorHTML'       => $FORM->{'separatorHTML'},
  });

  # remove unneeded keys
  &_removeUnneededSchemaKeys($IT::DB::Schema::SCHEMA_TABLES);

  # save schema
  &IT::DB::Schema::saveSchema($SCHEMA_FILEPATH);
  &IT::FileLock::releaseLock();

  ### return to field list page
  &IT::App::Menu::Admin::DatabaseEditor::TableEditor::editTable();

}

# ------------------------------------------------------------------------------
# Function    :
# Description :
# Usage       : my $highestFieldOrder = &_getHighestFieldOrder($tableName);
# ------------------------------------------------------------------------------

sub _getHighestFieldOrder {
  my $tableName = shift || die "no tablename specified!";

  # get highest field order
  my $highestDisplayOrder = 0;
  my @fieldNames = &IT::DB::Schema::getFieldNames($tableName, 'INCLUDE_SCHEMA_ONLY_FIELDS');
  foreach my $fieldName (@fieldNames) {
    my $appDisplayOrder = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appDisplayOrder');
    if ($appDisplayOrder > $highestDisplayOrder) { $highestDisplayOrder = $appDisplayOrder; }
  }

  return $highestDisplayOrder;

}

# ----------------------------------------------------------------------------
# Function    : addField
# Description : show table details and field list
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::addField();
# ----------------------------------------------------------------------------

sub addField {
  return &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::editField(@_);
}

# ----------------------------------------------------------------------------
# Function    : editField
# Description : display field edit page.  Loads data from FORM input first
#               (for maintaining state when errors are displayed) then schema
#               data
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::editField();
# ----------------------------------------------------------------------------

sub editField {
  my $errorMessage = shift;
  if ($errorMessage) { &IT::App::alert($errorMessage); }

  my $tableName  = $FORM->{'tableName'} || die "no tablename specified!";
  my $fieldName  = $FORM->{'fieldName'};
  my $isNewField = !$FORM->{'fieldName'};
  my $template   = "admin/menus/admin/databaseFieldEditor.html";

  # error checking
  my $appUserEditable = $isNewField ? '' : &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appUserEditable');
  if (!$isNewField && !$appUserEditable && !$SETTINGS->{'allowSystemFieldEditing'}) {
    my $errors = "Field Name '$fieldName' isn't a user editable field!";
    &IT::App::Menu::Admin::DatabaseEditor::TableEditor::editTable($errors);
  }

  # load data for existing fields
  my($pagePlaceholders, $appFieldLabel, $appFieldType, $appDisplayOnly, $appHiddenField, $dbFieldType, $appFieldOptions, $appFieldValidationRules, $appFieldSeparator, $listOptionsArray);
  my($appCustomFieldType);
  if (!$isNewField) {
    $appFieldLabel           = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldLabel');
    $appFieldType            = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldType');
    $appDisplayOnly          = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appDisplayOnly');
    $appHiddenField          = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appHiddenField');
    $dbFieldType             = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'type');
    $appCustomFieldType      = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appCustomFieldType');
    $appFieldOptions         = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldOptions');
    $appFieldValidationRules = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldValidationRules');
    $appFieldSeparator       = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldSeparator');
    $listOptionsArray        = $appFieldOptions->{'list_options'};
  }

  # set placeholder globals
  &IT::Template::setGlobalPlaceholder('menuGroup', 'databaseEditor');
  &IT::Template::setGlobalPlaceholder('tableName', $tableName);
  &IT::Template::setGlobalPlaceholder('fieldName', $fieldName);

  ### define placeholders
  $pagePlaceholders->{'appFieldLabel'}               = &firstDefinedValue($FORM->{'appFieldLabel'},      $appFieldLabel,      '');
  $pagePlaceholders->{'newFieldName'}                = &firstDefinedValue($FORM->{'newFieldName'},       $fieldName,          '');
  $pagePlaceholders->{'appFieldType'}                = &firstDefinedValue($FORM->{'appFieldType'},       $appFieldType,       'none');
  $pagePlaceholders->{'oldAppFieldType'}             = $appFieldType;
  $pagePlaceholders->{'appHiddenField'}              = &firstDefinedValue($FORM->{'appHiddenField'},     $appHiddenField,     '0');
  $pagePlaceholders->{'appDisplayOnly'}              = &firstDefinedValue($FORM->{'appDisplayOnly'},     $appDisplayOnly,     '0');
  $pagePlaceholders->{'appUserEditable'}             = &firstDefinedValue($FORM->{'appUserEditable'},    $appUserEditable,    $isNewField ? '1' : '0');
  $pagePlaceholders->{'dbFieldType'}                 = &firstDefinedValue($FORM->{'dbFieldType'},        $dbFieldType,        'memo');
  $pagePlaceholders->{'appCustomFieldType'}          = &firstDefinedValue($FORM->{'appCustomFieldType'}, $appCustomFieldType, '');
  $pagePlaceholders->{'appCustomFieldTypeOptions'}   = &_getCustomFieldTypePulldownOptions($pagePlaceholders->{'appCustomFieldType'});

  ###
  ### NOTE: Because we don't always store blank or zero values we need to be careful not to set a default value for fields that are not $isNewField
  ###

  # get appFieldOptions placeholders
  $pagePlaceholders->{'textfield_defaultValue'}      = &firstDefinedValue($FORM->{'textfield_defaultValue'},      $appFieldOptions->{'textfield_defaultValue'},      '');
  $pagePlaceholders->{'textfield_isPasswordField'}   = &firstDefinedValue($FORM->{'textfield_isPasswordField'},   $appFieldOptions->{'textfield_isPasswordField'},   '');
  $pagePlaceholders->{'textbox_defaultValue'}        = &firstDefinedValue($FORM->{'textbox_defaultValue'},        $appFieldOptions->{'textbox_defaultValue'},        '');
  $pagePlaceholders->{'textbox_heightInPixels'}      = &firstDefinedValue($FORM->{'textbox_heightInPixels'},      $appFieldOptions->{'textbox_heightInPixels'},      $isNewField ? '100' : '');
  $pagePlaceholders->{'textbox_isWysiwyg'}           = &firstDefinedValue($FORM->{'textbox_isWysiwyg'},           $appFieldOptions->{'textbox_isWysiwyg'},           $isNewField ? '1' : '0');
  $pagePlaceholders->{'textbox_noAutoFormat'}        = &firstDefinedValue($FORM->{'textbox_noAutoFormat'},        $appFieldOptions->{'textbox_noAutoFormat'},        '0');
  $pagePlaceholders->{'date_showHourMinFields'}      = &firstDefinedValue($FORM->{'date_showHourMinFields'},      $appFieldOptions->{'date_showHourMinFields'},      $isNewField ? '1' : '0');
  $pagePlaceholders->{'date_showSecondsField'}       = &firstDefinedValue($FORM->{'date_showSecondsField'},       $appFieldOptions->{'date_showSecondsField'},       $isNewField ? '1' : '0');
  $pagePlaceholders->{'list_singleOrMultipleChoice'} = &firstDefinedValue($FORM->{'list_singleOrMultipleChoice'}, $appFieldOptions->{'list_singleOrMultipleChoice'}, 'single');
  $pagePlaceholders->{'list_singleType'}             = &firstDefinedValue($FORM->{'list_singleType'},             $appFieldOptions->{'list_singleType'},             'pulldown');
  $pagePlaceholders->{'list_singleRows'}             = &firstDefinedValue($FORM->{'list_singleRows'},             $appFieldOptions->{'list_singleRows'},             $isNewField ? '1' : '0');
  $pagePlaceholders->{'list_multipleType'}           = &firstDefinedValue($FORM->{'list_multipleType'},           $appFieldOptions->{'list_multipleType'},           'checkboxes');
  $pagePlaceholders->{'list_multipleRows'}           = &firstDefinedValue($FORM->{'list_multipleRows'},           $appFieldOptions->{'list_multipleRows'},           $isNewField ? '5' : '0');
  $pagePlaceholders->{'checkbox_checkedByDefault'}   = &firstDefinedValue($FORM->{'checkbox_checkedByDefault'},   $appFieldOptions->{'checkbox_checkedByDefault'},   '0');
  $pagePlaceholders->{'checkbox_description'}        = &firstDefinedValue($FORM->{'checkbox_description'},        $appFieldOptions->{'checkbox_description'},        '');
  $pagePlaceholders->{'checkbox_checkedValue'}       = &firstDefinedValue($FORM->{'checkbox_checkedValue'},       $appFieldOptions->{'checkbox_checkedValue'},       $isNewField ? 'Yes' : '');
  $pagePlaceholders->{'checkbox_uncheckedValue'}     = &firstDefinedValue($FORM->{'checkbox_uncheckedValue'},     $appFieldOptions->{'checkbox_uncheckedValue'},     $isNewField ? 'No'  : '');
  $pagePlaceholders->{'upload_type'}                 = &firstDefinedValue($FORM->{'upload_type'},                 $appFieldOptions->{'upload_type'},                 'single');
  $pagePlaceholders->{'upload_maxUploads'}           = &firstDefinedValue($FORM->{'upload_maxUploads'},           $appFieldOptions->{'upload_maxUploads'},           $isNewField ? '999' : '');
  $pagePlaceholders->{'useCustomFieldHtml'}          = &firstDefinedValue($FORM->{'useCustomFieldHtml'},          $appFieldOptions->{'useCustomFieldHtml'},          '0');
  $pagePlaceholders->{'customFieldHtml'}             = &firstDefinedValue($FORM->{'customFieldHtml'},             $appFieldOptions->{'customFieldHtml'},          $isNewField ? $CUSTOM_FIELD_HTML_DEFAULT : '');

  # define dlist list options placeholders
  if (exists $FORM->{'list_options:maximum_rows_allowed'}) {  # 'list_options' won't get submitted if there is nothing in list so check this key instead
    $listOptionsArray = &_listOptions_getArrayFromFormData();
  }
  &_listOptions_defineGlobalPlaceholders( $listOptionsArray );  # defines global placeholders for dlist hidden input fields

  # get appValidationRules placeholders
  $pagePlaceholders->{'valueIsRequired'}                        = &firstDefinedValue($FORM->{'valueIsRequired'},                        $appFieldValidationRules->{'valueIsRequired'},                        '');
  $pagePlaceholders->{'valueMustBeUnique'}                      = &firstDefinedValue($FORM->{'valueMustBeUnique'},                      $appFieldValidationRules->{'valueMustBeUnique'},                      '');
  $pagePlaceholders->{'checkMinLength'}                         = &firstDefinedValue($FORM->{'checkMinLength'},                         $appFieldValidationRules->{'checkMinLength'},                         '');
  $pagePlaceholders->{'checkMaxLength'}                         = &firstDefinedValue($FORM->{'checkMaxLength'},                         $appFieldValidationRules->{'checkMaxLength'},                         '');
  $pagePlaceholders->{'minLength'}                              = &firstDefinedValue($FORM->{'minLength'},                              $appFieldValidationRules->{'minLength'},                              '');
  $pagePlaceholders->{'maxLength'}                              = &firstDefinedValue($FORM->{'maxLength'},                              $appFieldValidationRules->{'maxLength'},                              '');
  $pagePlaceholders->{'validationRuleType'}                     = &firstDefinedValue($FORM->{'validationRuleType'},                     $appFieldValidationRules->{'validationRuleType'},                     'none');
  $pagePlaceholders->{'validationRule_validElementName'}        = &firstDefinedValue($FORM->{'validationRule_validElementName'},        $appFieldValidationRules->{'validationRule_validElementName'},        '');
  $pagePlaceholders->{'validationRule_allowCharsLetter'}        = &firstDefinedValue($FORM->{'validationRule_allowCharsLetter'},        $appFieldValidationRules->{'validationRule_allowCharsLetter'},        '');
  $pagePlaceholders->{'validationRule_allowCharsNumber'}        = &firstDefinedValue($FORM->{'validationRule_allowCharsNumber'},        $appFieldValidationRules->{'validationRule_allowCharsNumber'},        '');
  $pagePlaceholders->{'validationRule_allowCharsOther'}         = &firstDefinedValue($FORM->{'validationRule_allowCharsOther'},         $appFieldValidationRules->{'validationRule_allowCharsOther'},         '');
  $pagePlaceholders->{'validationRule_allowCharsOtherValue'}    = &firstDefinedValue($FORM->{'validationRule_allowCharsOtherValue'},    $appFieldValidationRules->{'validationRule_allowCharsOtherValue'},    '');
  $pagePlaceholders->{'validationRule_disallowCharsLetter'}     = &firstDefinedValue($FORM->{'validationRule_disallowCharsLetter'},     $appFieldValidationRules->{'validationRule_disallowCharsLetter'},     '');
  $pagePlaceholders->{'validationRule_disallowCharsNumber'}     = &firstDefinedValue($FORM->{'validationRule_disallowCharsNumber'},     $appFieldValidationRules->{'validationRule_disallowCharsNumber'},     '');
  $pagePlaceholders->{'validationRule_disallowCharsOther'}      = &firstDefinedValue($FORM->{'validationRule_disallowCharsOther'},      $appFieldValidationRules->{'validationRule_disallowCharsOther'},      '');
  $pagePlaceholders->{'validationRule_disallowCharsOtherValue'} = &firstDefinedValue($FORM->{'validationRule_disallowCharsOtherValue'}, $appFieldValidationRules->{'validationRule_disallowCharsOtherValue'}, '');
  $pagePlaceholders->{'validationRule_perlRegexp'}              = &firstDefinedValue($FORM->{'validationRule_perlRegexp'},              $appFieldValidationRules->{'validationRule_perlRegexp'},              '');
  $pagePlaceholders->{'useCustomErrorForValidationRule'}        = &firstDefinedValue($FORM->{'useCustomErrorForValidationRule'},        $appFieldValidationRules->{'useCustomErrorForValidationRule'},        '');
  $pagePlaceholders->{'customValidationRuleError'}              = &firstDefinedValue($FORM->{'customValidationRuleError'},              $appFieldValidationRules->{'customValidationRuleError'},              '');
  $pagePlaceholders->{'uploadExtOverride'}                      = &firstDefinedValue($FORM->{'uploadExtOverride'},                      $appFieldValidationRules->{'uploadExtOverride'},                      '');
  $pagePlaceholders->{'uploadExtAllowed'}                       = &firstDefinedValue($FORM->{'uploadExtAllowed'},                       $appFieldValidationRules->{'uploadExtAllowed'},                       '');
  $pagePlaceholders->{'uploadFileSizeOverride'}                 = &firstDefinedValue($FORM->{'uploadFileSizeOverride'},                 $appFieldValidationRules->{'uploadFileSizeOverride'},                 '');
  $pagePlaceholders->{'uploadMaxFileSize'}                      = &firstDefinedValue($FORM->{'uploadMaxFileSize'},                      $appFieldValidationRules->{'uploadMaxFileSize'},                      '');
  $pagePlaceholders->{'uploadImageSizeOverride'}                = &firstDefinedValue($FORM->{'uploadImageSizeOverride'},                $appFieldValidationRules->{'uploadImageSizeOverride'},                '');
  $pagePlaceholders->{'uploadMaxImageHeight'}                   = &firstDefinedValue($FORM->{'uploadMaxImageHeight'},                   $appFieldValidationRules->{'uploadMaxImageHeight'},                   '');
  $pagePlaceholders->{'uploadMaxImageWidth'}                    = &firstDefinedValue($FORM->{'uploadMaxImageWidth'},                    $appFieldValidationRules->{'uploadMaxImageWidth'},                    '');
  $pagePlaceholders->{'uploadThumbSizeOverride'}                = &firstDefinedValue($FORM->{'uploadThumbSizeOverride'},                $appFieldValidationRules->{'uploadThumbSizeOverride'},                '');
  $pagePlaceholders->{'uploadMaxThumbHeight'}                   = &firstDefinedValue($FORM->{'uploadMaxThumbHeight'},                   $appFieldValidationRules->{'uploadMaxThumbHeight'},                   '');
  $pagePlaceholders->{'uploadMaxThumbWidth'}                    = &firstDefinedValue($FORM->{'uploadMaxThumbWidth'},                    $appFieldValidationRules->{'uploadMaxThumbWidth'},                    '');

  # get appFieldSeparator placeholders
  $pagePlaceholders->{'separatorType'}       = &firstDefinedValue($FORM->{'separatorType'},       $appFieldSeparator->{'separatorType'},       'blankLine');
  $pagePlaceholders->{'separatorHeaderText'} = &firstDefinedValue($FORM->{'separatorHeaderText'}, $appFieldSeparator->{'separatorHeaderText'}, '');
  $pagePlaceholders->{'separatorHTML'}       = &firstDefinedValue($FORM->{'separatorHTML'},       $appFieldSeparator->{'separatorHTML'},       $SEPARATOR_HTML_DEFAULT);

  ### display page
  &IT::App::displayInterface({
    'template'           => $template,
    'templatecell'       => 'editField',
    'selectedMenuOption' => 'admin',
    'menuGroup'          => 'databaseEditor',
    'placeholders'       => $pagePlaceholders,
  });
}

# ------------------------------------------------------------------------------
# Function    : _getCustomFieldTypePulldownOptions
# Description :
# Usage       : my $optionsHtml = &_getCustomFieldTypePulldownOptions($selectedCustomTypeName);
# ------------------------------------------------------------------------------

sub _getCustomFieldTypePulldownOptions {
  my $selectedCustomTypeName = shift;

  # generate options html
  my $optionsHtml;
  my $customFieldsByName = &IT::App::Menu::listCustomFieldTypes();
  foreach my $customFieldType (@$customFieldsByName) {
    my $name         = $customFieldType->{'name'};
    my $label        = $customFieldType->{'label'};
    my $isSelected   = $name eq $selectedCustomTypeName;
    my $selectedHtml = $isSelected ? 'selected="selected"' : "";
    $optionsHtml .= qq|<option value="$name" $selectedHtml>$label</option>\n|;
  }

  return $optionsHtml;
}

# ----------------------------------------------------------------------------
# Function    : saveField
# Description :
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::saveField();
# ----------------------------------------------------------------------------

sub saveField {
  my $tableName  = $FORM->{'tableName'} || die "no tablename specified!";
  my $fieldName  = $FORM->{'fieldName'} || $FORM->{'newFieldName'};
  my $template   = "admin/menus/admin/databaseTableEditor.html";
  my $isNewField = !$FORM->{'fieldName'};

  # Note: Allowing user to save perl regexp code in valation rules could allow for code injection
  &IT::App::demoModeAlertAndAction("Creating and updating fields are disabled in demo mode.", \&IT::App::Menu::Admin::DatabaseEditor::TableEditor::listTables);

  my $redisplayForm = $isNewField ? \&addField : \&editField;

  # display errors;
  my $errors = &_getSaveFieldErrors();
  if ($errors) { &{$redisplayForm}($errors); }

  &IT::FileLock::getLock('exclusive');

  ### get form values for appFieldOptions, appFieldValidationRules
  my($appFieldOptions, $appFieldValidationRules);
  foreach my $placeholder (@ALL_FIELD_EDITOR_FIELDS) {
    if    ($FIELD_OPTION_FIELDS{$placeholder})    { $appFieldOptions->{$placeholder}         = $FORM->{$placeholder}; }
    elsif ($VALIDATION_RULE_FIELDS{$placeholder}) { $appFieldValidationRules->{$placeholder} = $FORM->{$placeholder}; }
    else { die "unknown placeholder '$placeholder'!"; }
  }
  $appFieldOptions->{'list_options'} = &_listOptions_getArrayFromFormData();

  ### Create new field
  if ($isNewField) {
    &IT::DB::createField($tableName, $fieldName, $FORM->{'dbFieldType'});
    &IT::DB::Schema::createField($tableName, $fieldName, $FORM->{'dbFieldType'});
    my $appUserEditable = (exists $FORM->{'appUserEditable'}) ? $FORM->{'appUserEditable'} : 1;
    &IT::DB::Schema::setFieldAttribute($tableName, $fieldName, 'appUserEditable', $appUserEditable);
  }

  ### change field name and type (for editing existing fields)
  my $oldDbFieldType   = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'type');
  my $fieldNameChanged = $FORM->{'newFieldName'} ne $FORM->{'fieldName'};
  my $fieldTypeChanged = $FORM->{'dbFieldType'}  ne $oldDbFieldType;
  if (!$isNewField && ($fieldNameChanged || $fieldTypeChanged)) {
    &IT::DB::changeFieldNameAndType($tableName, $fieldName, $FORM->{'newFieldName'}, $FORM->{'dbFieldType'});
    &IT::DB::Schema::changeFieldNameAndType($tableName, $fieldName, $FORM->{'newFieldName'}, $FORM->{'dbFieldType'});
  }

  ### update schema
  if ($isNewField) {
    my $highestDisplayOrder = &_getHighestFieldOrder($tableName);
    my $fieldDisplayOrder   = $highestDisplayOrder + 10;
    &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appDisplayOrder',       $fieldDisplayOrder);
  }
  &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appFieldLabel',           $FORM->{'appFieldLabel'});
  &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appFieldType',            $FORM->{'appFieldType'});
  &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appCustomFieldType',      $FORM->{'appCustomFieldType'});
  &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appDisplayOnly',          $FORM->{'appDisplayOnly'});
  &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appHiddenField',          $FORM->{'appHiddenField'});
  &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appUserEditable',         $FORM->{'appUserEditable'}) if exists $FORM->{'appUserEditable'};
  &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appFieldOptions',         $appFieldOptions);
  &IT::DB::Schema::setFieldAttribute($tableName, $FORM->{'newFieldName'}, 'appFieldValidationRules', $appFieldValidationRules);

  # remove unneeded keys
  &_removeUnneededSchemaKeys($IT::DB::Schema::SCHEMA_TABLES);

  # save schema
  &IT::DB::Schema::saveSchema($SCHEMA_FILEPATH);

  &IT::FileLock::releaseLock();

  ### return to field list page
  &IT::App::Menu::Admin::DatabaseEditor::TableEditor::editTable();

}


# ------------------------------------------------------------------------------
# Function    : _removeUnneededSchemaKeys
# Description : remove unneeded keys to reduce file and memory size
# Usage       : &_removeUnneededSchemaKeys($IT::DB::Schema::SCHEMA_TABLES);
# ------------------------------------------------------------------------------

sub _removeUnneededSchemaKeys {
  my $schemaTables = shift || die "no schema tables specified!";

  foreach my $tableName (keys %$schemaTables) {
    foreach my $fieldDef (@{$schemaTables->{$tableName}{'fields'}}) {
      my $fieldType          = $fieldDef->{'appFieldType'};
      my $fieldOptions       = $fieldDef->{'appFieldOptions'};
      my $validationRules    = $fieldDef->{'appFieldValidationRules'};
      my $validationRuleType = $validationRules->{'validationRuleType'};

      # all field types
      if (!$fieldDef->{'appCustomFieldType'})               { delete $fieldDef->{'appCustomFieldType'}; }
      if ($fieldDef->{'appFieldLabel'} eq '')               { delete $fieldDef->{'appFieldLabel'}; }
      if (!$fieldDef->{'appUserEditable'})                  { delete $fieldDef->{'appUserEditable'}; }
      if (!$fieldDef->{'appHiddenField'})                   { delete $fieldDef->{'appHiddenField'}; }
      if (!$fieldDef->{'appDisplayOnly'})                   { delete $fieldDef->{'appDisplayOnly'}; }
      if ($fieldOptions->{'textfield_defaultValue'} eq '')  { delete $fieldOptions->{'textfield_defaultValue'}; }
      if (!$fieldOptions->{'textfield_isPasswordField'})    { delete $fieldOptions->{'textfield_isPasswordField'}; }
      if (!$fieldOptions->{'list_singleRows'})              { delete $fieldOptions->{'list_singleRows'}; }
      if (!$fieldOptions->{'checkbox_checkedByDefault'})    { delete $fieldOptions->{'checkbox_checkedByDefault'}; }
      if ($fieldOptions->{'checkbox_checkedValue'} eq '')   { delete $fieldOptions->{'checkbox_checkedValue'}; }
      if ($fieldOptions->{'checkbox_description'} eq '')    { delete $fieldOptions->{'checkbox_description'}; }
      if ($fieldOptions->{'checkbox_uncheckedValue'} eq '') { delete $fieldOptions->{'checkbox_uncheckedValue'}; }
      if ($fieldOptions->{'textbox_defaultValue'} eq '')    { delete $fieldOptions->{'textbox_defaultValue'}; }
      if (!$fieldOptions->{'textbox_isWysiwyg'})            { delete $fieldOptions->{'textbox_isWysiwyg'}; }
      if (!$fieldOptions->{'textbox_noAutoFormat'})         { delete $fieldOptions->{'textbox_noAutoFormat'}; }
      if (!$validationRules->{'valueIsRequired'})           { delete $validationRules->{'valueIsRequired'}; }

      if (!$fieldOptions->{'useCustomFieldHtml'}) {
        delete $fieldOptions->{'useCustomFieldHtml'};
        delete $fieldOptions->{'customFieldHtml'};
      }
      if (!$validationRules->{'valueMustBeUnique'}) { delete $validationRules->{'valueMustBeUnique'}; }
      if (!$validationRules->{'validationRuleType'}) { delete $validationRules->{'validationRuleType'}; }
      if (!$validationRules->{'checkMinLength'}) {
        delete $validationRules->{'checkMinLength'};
        delete $validationRules->{'minLength'};
      }
      if (!$validationRules->{'checkMaxLength'}) {
        delete $validationRules->{'checkMaxLength'};
        delete $validationRules->{'maxLength'};
      }
      if ($validationRuleType eq 'none')    {
        delete $validationRules->{'validationRuleType'};
        delete $validationRules->{'validationRule_allowCharsLetter'};
        delete $validationRules->{'validationRule_allowCharsNumber'};
        delete $validationRules->{'validationRule_allowCharsOther'};
        delete $validationRules->{'validationRule_allowCharsOtherValue'};
        delete $validationRules->{'validationRule_disallowCharsLetter'};
        delete $validationRules->{'validationRule_disallowCharsNumber'};
        delete $validationRules->{'validationRule_disallowCharsOther'};
        delete $validationRules->{'validationRule_disallowCharsOtherValue'};
        delete $validationRules->{'validationRule_perlRegexp'};
        delete $validationRules->{'validationRule_validElementName'};
      }
      if ($validationRuleType ne 'element') { delete $validationRules->{'validationRule_validElementName'}; }
      if ($validationRuleType ne 'allowChars') {
        delete $validationRules->{'validationRule_allowCharsLetter'};
        delete $validationRules->{'validationRule_allowCharsNumber'};
        delete $validationRules->{'validationRule_allowCharsOther'};
        delete $validationRules->{'validationRule_allowCharsOtherValue'};
      }
      if ($validationRuleType ne 'disallowChars') {
        delete $validationRules->{'validationRule_disallowCharsLetter'};
        delete $validationRules->{'validationRule_disallowCharsNumber'};
        delete $validationRules->{'validationRule_disallowCharsOther'};
        delete $validationRules->{'validationRule_disallowCharsOtherValue'};

      }
      if ($validationRuleType ne 'perlRegexp') {
        delete $validationRules->{'validationRule_perlRegexp'};
      }
      if ($validationRuleType ne 'element') { delete $validationRules->{'validationRule_validElementName'}; }

      if (!$validationRules->{'useCustomErrorForValidationRule'}) {
        delete $validationRules->{'useCustomErrorForValidationRule'};
        delete $validationRules->{'customValidationRuleError'};
      }

      # fieldtype: none
      if ($fieldType eq 'none') {
        delete $fieldDef->{'appFieldOptions'};
        delete $fieldDef->{'appFieldValidationRules'};
      }

      # fieldtype: custom
      if ($fieldType eq 'custom') {
        delete $fieldDef->{'appFieldOptions'};
        delete $fieldDef->{'appFieldValidationRules'};
      }

      # textfield
      if ($fieldType eq 'textfield') {
      }
      if ($fieldType ne 'textfield') {
        delete $fieldOptions->{'textfield_defaultValue'};
        delete $fieldOptions->{'textfield_isPasswordField'};

        delete $validationRules->{'validationRuleType'};
        delete $validationRules->{'validationRule_allowCharsLetter'};
        delete $validationRules->{'validationRule_allowCharsNumber'};
        delete $validationRules->{'validationRule_allowCharsOther'};
        delete $validationRules->{'validationRule_allowCharsOtherValue'};
        delete $validationRules->{'validationRule_disallowCharsLetter'};
        delete $validationRules->{'validationRule_disallowCharsNumber'};
        delete $validationRules->{'validationRule_disallowCharsOther'};
        delete $validationRules->{'validationRule_disallowCharsOtherValue'};
        delete $validationRules->{'validationRule_perlRegexp'};
        delete $validationRules->{'validationRule_validElementName'};
        delete $validationRules->{'useCustomErrorForValidationRule'};
        delete $validationRules->{'customValidationRuleError'};
      }

      # textbox
      if ($fieldType ne 'textbox') {
        delete $fieldOptions->{'textbox_defaultValue'};
        delete $fieldOptions->{'textbox_heightInPixels'};
        delete $fieldOptions->{'textbox_isWysiwyg'};
        delete $fieldOptions->{'textbox_noAutoFormat'};
      }

      # date
      if ($fieldType eq 'date') {
        if (!$fieldOptions->{'date_showHourMinFields'}) { delete $fieldOptions->{'date_showHourMinFields'}; }
        if (!$fieldOptions->{'date_showSecondsField'})  { delete $fieldOptions->{'date_showSecondsField'}; }
        foreach my $ruleName (keys %$validationRules) {
          if ($ruleName ne 'valueMustBeUnique') { delete $validationRules->{$ruleName}; }
        }
      }
      elsif ($fieldType ne 'date') {
        delete $fieldOptions->{'date_showHourMinFields'};
        delete $fieldOptions->{'date_showSecondsField'};
      }

      # list
      if ($fieldType eq 'list') {
        if ($fieldOptions->{'list_singleOrMultipleChoice'} ne 'single') {
          delete $fieldOptions->{'list_singleType'};
          delete $fieldOptions->{'list_singleRows'};
        }
        elsif ($fieldOptions->{'list_singleOrMultipleChoice'} ne 'multiple') {
          delete $fieldOptions->{'list_multipleType'};
          delete $fieldOptions->{'list_multipleRows'};
        }
        foreach my $ruleName (keys %$validationRules) {
          if ($ruleName ne 'valueIsRequired' && $ruleName ne 'valueMustBeUnique') { delete $validationRules->{$ruleName}; }
        }
      }
      elsif ($fieldType ne 'list') {
        delete $fieldOptions->{'list_singleOrMultipleChoice'};
        delete $fieldOptions->{'list_singleType'};
        delete $fieldOptions->{'list_singleRows'};
        delete $fieldOptions->{'list_multipleType'};
        delete $fieldOptions->{'list_multipleRows'};
        delete $fieldOptions->{'list_options'};
      }

      # checkbox
      if ($fieldType eq 'checkbox') {
        delete $fieldDef->{'appFieldValidationRules'};
      }
      if ($fieldType ne 'checkbox') {
        delete $fieldOptions->{'checkbox_checkedByDefault'};
        delete $fieldOptions->{'checkbox_checkedValue'};
        delete $fieldOptions->{'checkbox_description'};
        delete $fieldOptions->{'checkbox_uncheckedValue'};
      };

      # upload
      if ($fieldType eq 'upload') {
        if ($fieldOptions->{'upload_type'} ne 'attachments' &&
            $fieldOptions->{'upload_type'} ne 'media') { delete $fieldOptions->{'upload_maxUploads'}; }
        if (!$validationRules->{'uploadExtOverride'}) {
          delete $validationRules->{'uploadExtOverride'};
          delete $validationRules->{'uploadExtAllowed'};
        }
        if (!$validationRules->{'uploadFileSizeOverride'}) {
          delete $validationRules->{'uploadFileSizeOverride'};
          delete $validationRules->{'uploadMaxFileSize'};
        }
        if (!$validationRules->{'uploadImageSizeOverride'}) {
          delete $validationRules->{'uploadImageSizeOverride'};
          delete $validationRules->{'uploadMaxImageHeight'};
          delete $validationRules->{'uploadMaxImageWidth'};
        }
        if (!$validationRules->{'uploadThumbSizeOverride'}) {
          delete $validationRules->{'uploadThumbSizeOverride'};
          delete $validationRules->{'uploadMaxThumbHeight'};
          delete $validationRules->{'uploadMaxThumbWidth'};
        }
      }
      elsif ($fieldType ne 'upload') {
        delete $fieldOptions->{'upload_type'};
        delete $fieldOptions->{'upload_maxUploads'};

        delete $validationRules->{'uploadExtOverride'};
        delete $validationRules->{'uploadExtAllowed'};
        delete $validationRules->{'uploadFileSizeOverride'};
        delete $validationRules->{'uploadMaxFileSize'};
        delete $validationRules->{'uploadImageSizeOverride'};
        delete $validationRules->{'uploadMaxImageHeight'};
        delete $validationRules->{'uploadMaxImageWidth'};
        delete $validationRules->{'uploadThumbSizeOverride'};
        delete $validationRules->{'uploadMaxThumbHeight'};
        delete $validationRules->{'uploadMaxThumbWidth'};
      }

      ### Separator
      if ($fieldType eq 'separator') {
        delete $fieldDef->{'appFieldValidationRules'};
        delete $fieldDef->{'appFieldSeparator'}{'separatorPosition'};
      }
      elsif ($fieldType ne 'separator') {
        delete $fieldDef->{'appFieldSeparator'};
      }

      ### remove empty hash keys
      if (!scalar keys %{$fieldDef->{'appFieldOptions'}})    { delete $fieldDef->{'appFieldOptions'}; }
      if (!scalar keys %{$fieldDef->{'appValidationRules'}}) { delete $fieldDef->{'appValidationRules'}; }

    }
  }

}

# ----------------------------------------------------------------------------
# Function    : _getSaveFieldErrors
# Description :
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::_getSaveFieldErrors();
# ----------------------------------------------------------------------------

sub _getSaveFieldErrors {
  my $tableName  = $FORM->{'tableName'} || die "no tablename specified!";
  my $fieldName  = $FORM->{'fieldName'} || $FORM->{'newFieldName'};
  my $isNewField = !$FORM->{'fieldName'};
  my @errors;

  # check if field is user editable
  if (!$isNewField && !&IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appUserEditable') && !$SETTINGS->{'allowSystemFieldEditing'}) {
    return "Field Name $fieldName isn't a user editable field!\n";
  }

  # newFieldName - copied error checking from &IT::DB::Common::dieOnInvalidFieldName()
  if    ($FORM->{'newFieldName'} eq '')                                         { return "Field Name can't be blank!\n"; }
  elsif ($FORM->{'newFieldName'} =~ /[^a-zA-Z0-9_]/)                            { push @errors, "Field Name '$FORM->{'newFieldName'}' can only contain the following characters (a-z, A-Z, 0-9, _)"; }
  if (length $FORM->{'newFieldName'} > $IT::DB::Globals::MAX_FIELDNAME_LENGTH)  { push @errors, "Field Name '$FORM->{'newFieldName'}' cannot exceed $IT::DB::Globals::MAX_FIELDNAME_LENGTH characters"; }
  if (&IT::DB::Common::isReservedWord($FORM->{'newFieldName'}))                 { push @errors, "Field Name '$FORM->{'newFieldName'}' cannot be a reserved word"; }

  # check if new fieldname already exists
  my $hasFieldNameChanged       = $FORM->{'newFieldName'} ne $FORM->{'fieldName'};
  my $newFieldNameAlreadyExists = &IT::DB::Schema::existsField($tableName, $FORM->{'newFieldName'});
  if ($hasFieldNameChanged && $newFieldNameAlreadyExists)                       { push @errors, "Field Name '$FORM->{'newFieldName'}' already exists in this table, choose another name"; }

  # appFieldType
  if ($FORM->{'appFieldType'} eq '')                                            { push @errors, "Field Type can't be blank!"; }
  elsif (!$VALID_APP_FIELD_TYPES{ $FORM->{'appFieldType'} })                       { push @errors, "Field Type '$FORM->{'appFieldType'}' is invalid!"; }
  if ($FORM->{'appFieldType'} eq 'date'   && $FORM->{'dbFieldType'} ne 'int')   { push @errors, "Field Type of 'date' can only be 'Stored As' an integer!"; }
  if ($FORM->{'appFieldType'} eq 'upload' && $FORM->{'dbFieldType'} ne 'memo')  { push @errors, "Field Type of 'upload' can only be 'Stored As' a memo!"; }
  if ($FORM->{'appFieldType'} eq 'list'   && $FORM->{'dbFieldType'} eq 'int')   { push @errors, "Field Type of 'list' can not be stored as an int!"; }
  if ($FORM->{'appFieldType'} eq 'custom' && !$FORM->{'appCustomFieldType'})    { push @errors, "You must specify a custom field type for 'custom' fields!"; }

  ### Database Options
  if    ($FORM->{'dbFieldType'} eq '')                                          { push @errors, "Stored As can't be blank!"; }
  elsif (!$VALID_FIELD_TYPES{ $FORM->{'dbFieldType'} })                         { push @errors, "Stored As '$FORM->{'dbFieldType'}' is invalid!"; }
  if ($FORM->{'index'})                                                         { push @errors, "User defined indexing is not yet supported!"; }

  ### Field Options
  if ($FORM->{'textbox_heightInPixels'} =~ /[^0-9]/)                            { push @errors, "Field Height isn't a number!"; }
  if ($FORM->{'textbox_heightInPixels'} ne '' &&
      $FORM->{'textbox_heightInPixels'} < 50)                                   { push @errors, "Field Height can't be less than 50 pixels!"; }
  if ($FORM->{'list_singleRows'} =~ /[^0-9]/)                                   { push @errors, "Single Choice Listbox Rows isn't a number!"; }
  if ($FORM->{'list_multipleRows'} =~ /[^0-9]/)                                 { push @errors, "Multiple Choice Listbox Rows isn't a number!"; }
  if ($FORM->{'appFieldType'} eq 'upload' && !$FORM->{'upload_type'})           { push @errors, "You must specify an upload type!"; }
  if ($FORM->{'upload_maxUploads'} =~ /[^0-9]/)                                 { push @errors, "Max Uploads isn't a number!"; }

  ### Input Validation
  if ($FORM->{'minLength'} =~ /[^0-9]/)                                         { push @errors, "Min Length isn't a number!"; }
  if ($FORM->{'maxLength'} =~ /[^0-9]/)                                         { push @errors, "Max Length isn't a number!"; }
  if ($FORM->{'validationRule_allowCharsOtherValue'} =~ /-./)                   { push @errors, "Only allow characters: Other Chars can only contain a dash if it's the last character, you must list each character individually!"; }
  if ($FORM->{'validationRule_disallowCharsOtherValue'} =~ /-./)                { push @errors, "Disallow characters - Other Chars can only contain a dash if it's the last character, you must list each character individually!"; }
  eval { m/$FORM->{'validationRule_perlRegexp'}/ };
  if ($@)                                                                       { push @errors, "Perl Regular Expression has one or more errors"; }
  if ($FORM->{'uploadExtAllowed'} &&
      $FORM->{'uploadExtAllowed'} !~ /^([\w\-]+,\s*)*[\w\-]+$/i)                { push @errors, "Upload Extensions can only be alphanumeric and must be in the following format: 'ext, ext, ext'"; }
  if ($FORM->{'uploadMaxFileSize'} =~ /[^0-9]/)                                 { push @errors, "Maximum Upload Filesize (KB) isn't a number!"; }
  if ($FORM->{'uploadMaxImageHeight'} =~ /[^0-9]/)                              { push @errors, "Maximum Image Height isn't a number!"; }
  if ($FORM->{'uploadMaxImageWidth'} =~ /[^0-9]/)                               { push @errors, "Maximum Image Width isn't a number!"; }
  if ($FORM->{'uploadMaxThumbHeight'} =~ /[^0-9]/)                              { push @errors, "Maximum Thumbnail Height isn't a number!"; }
  if ($FORM->{'uploadMaxThumbWidth'} =~ /[^0-9]/)                               { push @errors, "Maximum Thumbnail Width isn't a number!"; }

  ### custom field error checking
  if ($FORM->{'appFieldType'} eq 'custom' && $FORM->{'appCustomFieldType'}) {
    my $customFieldAttributes = &IT::App::Menu::getCustomFieldType( $FORM->{'appCustomFieldType'} );

    # check for valid DB field type
    my $validDbFieldTypes = $customFieldAttributes->{'validDatabaseFieldTypes'};
    if ($validDbFieldTypes) {
      my %validDbFieldTypes     = map { $_ => 1 } @$validDbFieldTypes;
      my $isValidDbType         = $validDbFieldTypes{ $FORM->{'dbFieldType'} };
      unless ($isValidDbType) {
       my $validDbTypesAsCSV = join ", ", @$validDbFieldTypes;
         push @errors, "Invalid value for \"Database Options > Stored As\".";
         push @errors, "Valid values for \"$customFieldAttributes->{'label'}\" are: $validDbTypesAsCSV";
      }
    }

    # run custom error checking code
    my $errorCheckingForFieldEditor = $customFieldAttributes->{'errorCheckingForFieldEditor'};
    if ($errorCheckingForFieldEditor) {
      push @errors, &{$errorCheckingForFieldEditor}($customFieldAttributes);
    }

  }


  return join("\n", @errors);
}

# ----------------------------------------------------------------------------
# Function    : confirmEraseField
# Description : display confirm erase page when the user clicks to erase a field
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::confirmEraseField();
# ----------------------------------------------------------------------------

sub confirmEraseField {
  my $tableName  = $FORM->{'tableName'} || die "no tablename specified!";
  my $fieldName  = $FORM->{'fieldName'};
  my $template   = "admin/menus/admin/databaseFieldEditor.html";

  # error checking
  if (!&IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appUserEditable') && !$SETTINGS->{'allowSystemFieldEditing'}) {
    &IT::App::alert("Field Name '$fieldName' isn't a user editable field!");
    &IT::App::Menu::Admin::DatabaseEditor::TableEditor::editTable();
  }

  # set placeholders
  &IT::Template::setGlobalPlaceholder('menuGroup', 'databaseEditor');
  &IT::Template::setGlobalPlaceholder('tableName', $tableName);
  &IT::Template::setGlobalPlaceholder('fieldName', $fieldName);

  # define placeholders
  my $appFieldSeparator = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldSeparator');
  my $pagePlaceholders = {};
  $pagePlaceholders->{'appFieldLabel'} = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldLabel');
  $pagePlaceholders->{'appFieldType'}  = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appFieldType');
  $pagePlaceholders->{'dbFieldType'}   = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'type');
  $pagePlaceholders->{'separatorType'} = $appFieldSeparator->{'separatorType'};

  ### display page
  &IT::App::displayInterface({
    'template'           => $template,
    'templatecell'       => 'confirmEraseField',
    'selectedMenuOption' => 'admin',
    'menuGroup'          => 'confirmFieldErase',
    'placeholders'       => $pagePlaceholders,
  });
}

# ----------------------------------------------------------------------------
# Function    : eraseField
# Description : erase a field
# Usage       : &IT::App::Menu::Admin::DatabaseEditor::FieldEditor::eraseField();
# ----------------------------------------------------------------------------

sub eraseField {
  my $tableName  = $FORM->{'tableName'} || die "no tablename specified!";
  my $fieldName  = $FORM->{'fieldName'};
  my $template   = "admin/menus/admin/databaseTableEditor.html";

  &IT::App::demoModeAlertAndAction("Erasing fields is disabled in demo mode.", \&IT::App::Menu::Admin::DatabaseEditor::TableEditor::listTables);

  # error checking
  if (!&IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'appUserEditable') && !$SETTINGS->{'allowSystemFieldEditing'}) {
    &IT::App::alert("Field Name '$fieldName' isn't a user editable field!");
    &IT::App::Menu::Admin::DatabaseEditor::TableEditor::editTable();
  }

  ### Erase field from datastore and schema
  &IT::FileLock::getLock('exclusive');
  my $dbFieldType = &IT::DB::Schema::getFieldAttributes($tableName, $fieldName, 'type');
  &IT::DB::dropField($tableName, $fieldName) unless ($dbFieldType eq 'none');
  &IT::DB::Schema::dropField($tableName, $fieldName);
  &IT::DB::Schema::saveSchema($SCHEMA_FILEPATH);
  &IT::FileLock::releaseLock();

  # return to field list page
  &IT::App::Menu::Admin::DatabaseEditor::TableEditor::editTable();
}

# ------------------------------------------------------------------------
# Function    : _listOptions_defineGlobalPlaceholders
# Description : define dlist placeholders for hidden fields and highest record number
# Usage       : &_listOptions_defineGlobalPlaceholders($listOptionsArray);
# ------------------------------------------------------------------------

sub _listOptions_defineGlobalPlaceholders {
  my $savedData = shift || [];
  my $listname  = 'list_options';

  my $listStruct = {};
  my $rowCounter = 0;
  foreach my $row (@$savedData) {
    $rowCounter++;
    $listStruct->{'rows'}{$rowCounter} = {  # this is the data structure required by DHTML_Lists
      '_num'              => $rowCounter,
      '_order'            => $rowCounter,
      '_erased'           => '0',
      $listname.'Label'   => $row->{'label'},
      $listname.'Value'   => $row->{'value'},
      $listname.'Default' => $row->{'selectedByDefault'},
    };
  }
  my $hiddenFields = '';
  if (keys %$listStruct) {
    $hiddenFields = &IT::DhtmlFieldLists::createHiddenRowFields($listname, $listStruct);
  }

  # create global placeholders
  &IT::Template::setGlobalPlaceholder($listname . '_hidden_fields', $hiddenFields);
  &IT::Template::setGlobalPlaceholder($listname . '_highest_record_number', $rowCounter);
}

# ------------------------------------------------------------------------
# Function    : _listOptions_getHashFromFormData
# Description : return array of hashrefs from form data
# Usage       : my $listOptionsArray = &_listOptions_getArrayFromFormData()
# ------------------------------------------------------------------------

sub _listOptions_getArrayFromFormData {
  my $listname = 'list_options';
  my $listOptions = [];

  # if no form data return empty array
  unless ($FORM->{$listname}) { return $listOptions; }

  # convert dhtmlList listStructure to simple name/value hash
  my $listStruct = &IT::DhtmlFieldLists::getListStructFromFormData($listname);


  foreach my $rowNum (sort { $listStruct->{'rows'}{$a}{'_order'} <=> $listStruct->{'rows'}{$b}{'_order'} }
                      keys %{$listStruct->{'rows'}}) {
    my $thisRow = $listStruct->{'rows'}{$rowNum};

    my $option = {};
    $option->{'label'}             = $thisRow->{$listname . 'Label'};
    $option->{'value'}             = $thisRow->{$listname . 'Value'};
    $option->{'selectedByDefault'} = $thisRow->{$listname . 'Default'};

    push @$listOptions, $option;
  }

  return $listOptions;
}

# ----------------------------------------------------------------------------
# end of file
1;

