<?PHP
#
#   FILE:  MetadataField.php
#
#   Part of the Collection Workflow Integration System (CWIS)
#   Copyright 2012-2013 Edward Almasy and Internet Scout Research Group
#   http://scout.wisc.edu/cwis/
#

/**
* Object representing a locally-defined type of metadata field.
*/
class MetadataField {

    # ---- PUBLIC INTERFACE --------------------------------------------------

    # Update methods for timestamp fields
    const UPDATEMETHOD_NOAUTOUPDATE   = "NoAutoUpdate";
    const UPDATEMETHOD_ONRECORDCREATE = "OnRecordCreate";
    const UPDATEMETHOD_BUTTON         = "Button";
    const UPDATEMETHOD_ONRECORDEDIT   = "OnRecordEdit";
    const UPDATEMETHOD_ONRECORDCHANGE = "OnRecordChange";

    # values for the *UserIsValue fields
    const USERISVALUE_OR = -1;
    const USERISVALUE_UNSET = 0;
    const USERISVALUE_AND = 1;

    /**
    * Get current error status of object.
    * @return int Error status value drawn from MDFSTAT constants defined
    *       in the MetadataSchema class.
    */
    function Status() {  return $this->ErrorStatus;  }

    /**
    * Get/set type of metadata field (enumerated value).  Types are MDFTYPE_
    * constants defined in the MetadataSchema class.
    * @param enum $NewValue New type for field.  (OPTIONAL)
    * @return enum Current type for field.
    */
    function Type($NewValue = DB_NOVALUE)
    {
        # if new value supplied
        if (($NewValue != DB_NOVALUE)
             && ($NewValue != MetadataField::$FieldTypePHPEnums[
                    $this->DBFields["FieldType"]]))
        {
            # update database fields and store new type
            $this->ModifyField(NULL, $NewValue);
        }

        # return type to caller
        return MetadataField::$FieldTypePHPEnums[$this->DBFields["FieldType"]];
    }

    /**
    * Get type of field as string.
    * @return string Field type.
    */
    function TypeAsName()
    {
        return $this->DBFields["FieldType"];
    }

    /**
    * Get ID of schema for field.
    * @return int Schema ID.
    */
    function SchemaId()
    {
        return $this->DBFields["SchemaId"];
    }

    /**
    * Get display name for field.  Returns label if available, or field name
    * if label is not set for field.
    * @return string Display name.
    */
    function GetDisplayName()
    {
        return strlen($this->Label()) ? $this->Label() : $this->Name();
    }

    /**
    * Get/set name of field.  Field names are limited to alphanumerics, spaces,
    * and parentheses.
    * @param string $NewName New field name.  (OPTIONAL)
    * @return string Current field name.
    */
    function Name($NewName = DB_NOVALUE)
    {
        # if new name specified
        if ($NewName != DB_NOVALUE
            && trim($NewName) != $this->DBFields["FieldName"])
        {
            $NewName = trim($NewName);
            $NormalizedName = $this->NormalizeFieldNameForDB(strtolower($NewName));

            # if field name is invalid
            if (!preg_match("/^[[:alnum:] \(\)]+$/", $NewName))
            {
                # set error status to indicate illegal name
                $this->ErrorStatus = MetadataSchema::MDFSTAT_ILLEGALNAME;
            }

            # if the new name is a reserved word
            else if ($NormalizedName == "resourceid" || $NormalizedName == "schemaid")
            {
                # set error status to indicate illegal name
                $this->ErrorStatus = MetadataSchema::MDFSTAT_ILLEGALNAME;
            }

            # the name is okay but might be a duplicate
            else
            {
                # check for duplicate name
                $DuplicateCount = $this->DB->Query(
                    "SELECT COUNT(*) AS RecordCount FROM MetadataFields"
                            ." WHERE FieldName = '".addslashes($NewName)."'"
                            ." AND SchemaId = ".intval($this->DBFields["SchemaId"]),
                    "RecordCount");

                # if field name is duplicate
                if ($DuplicateCount > 0)
                {
                    # set error status to indicate duplicate name
                    $this->ErrorStatus = MetadataSchema::MDFSTAT_DUPLICATENAME;
                }
                else
                {
                    # modify database declaration to reflect new field name
                    $this->ErrorStatus = MetadataSchema::MDFSTAT_OK;
                    $this->ModifyField($NewName);
                }
            }
        }

        # return value to caller
        return $this->DBFields["FieldName"];
    }

    /**
    * Get/set label for field.
    * @param string $NewLabel New label for field.  (OPTIONAL)
    * @return string Current label for field.
    */
    function Label($NewLabel = DB_NOVALUE)
    {
        $ValidValueExp = '/^[[:alnum:] ]*$/';
        $Value = $this->DBFields["Label"];

        # if a new label was specified
        if ($NewLabel !== DB_NOVALUE && trim($NewLabel) != $Value)
        {
            $NewLabel = trim($NewLabel);

            # if field label is valid
            if (preg_match($ValidValueExp, $NewLabel))
            {
                $this->UpdateValue("Label", $NewLabel);
                $Value = $NewLabel;
            }
            # the field label is invalid
            else
            {
                $this->ErrorStatus = MetadataSchema::MDFSTAT_ILLEGALLABEL;
            }
        }

        return $Value;
    }

    /**
    * Get metadata field types that this field can be converted to.
    * @return array Array with constants (MDFTYPE_ values) for the index and
    *       field type strings for the values.
    */
    function GetAllowedConversionTypes()
    {
        # determine type list based on our type
        switch ($this->Type())
        {
            case MetadataSchema::MDFTYPE_TEXT:
            case MetadataSchema::MDFTYPE_PARAGRAPH:
            case MetadataSchema::MDFTYPE_NUMBER:
            case MetadataSchema::MDFTYPE_FLAG:
            case MetadataSchema::MDFTYPE_URL:
                $AllowedTypes = array(
                        MetadataSchema::MDFTYPE_TEXT       => "Text",
                        MetadataSchema::MDFTYPE_PARAGRAPH  => "Paragraph",
                        MetadataSchema::MDFTYPE_NUMBER     => "Number",
                        MetadataSchema::MDFTYPE_FLAG       => "Flag",
                        MetadataSchema::MDFTYPE_URL        => "Url"
                    );
                break;

            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
                $AllowedTypes = array(
                        MetadataSchema::MDFTYPE_CONTROLLEDNAME => "ControlledName",
                        MetadataSchema::MDFTYPE_OPTION         => "Option",
                    );
                break;

            case MetadataSchema::MDFTYPE_DATE:
                $AllowedTypes = array(
                        MetadataSchema::MDFTYPE_TEXT  => "Text",
                        MetadataSchema::MDFTYPE_DATE  => "Date",
                    );
                break;

            case MetadataSchema::MDFTYPE_IMAGE:
                $AllowedTypes = array(
                        MetadataSchema::MDFTYPE_TEXT  => "Text",
                        MetadataSchema::MDFTYPE_IMAGE => "Still Image",
                    );
                break;

            case MetadataSchema::MDFTYPE_TIMESTAMP:
            case MetadataSchema::MDFTYPE_TREE:
            case MetadataSchema::MDFTYPE_USER:
            case MetadataSchema::MDFTYPE_FILE:
            case MetadataSchema::MDFTYPE_REFERENCE:
            default:
                $AllowedTypes = array();
                break;
        }

        # return type list to caller
        return $AllowedTypes;
    }

    /**
    * Get/set whether field is temporary instance.
    * @param bool $NewSetting If TRUE, field is a temporary instance, or
    *       if FALSE, field is non-temporary.  (OPTIONAL)
    * @return bool If TRUE, field is a temporary instance, or
    *       if FALSE, field is non-temporary.
    */
    function IsTempItem($NewSetting = NULL)
    {
        $Schema = new MetadataSchema($this->SchemaId());
        $ItemTableName = "MetadataFields";
        $ItemIdFieldName = "FieldId";
        $ItemFactoryObjectName = "MetadataSchema";
        $ItemAssociationTables = array(
                "FieldQualifierInts",
                );
        $ItemAssociationFieldName = "MetadataFieldId";

        # if new temp item setting supplied
        if (!is_null($NewSetting))
        {
            # if caller requested to switch
            if (($this->Id() < 0 && $NewSetting == FALSE)
                || ($this->Id() >= 0 && $NewSetting == TRUE))
            {
                # if field name is invalid
                if (strlen($this->NormalizeFieldNameForDB($this->Name())) < 1)
                {
                    # set error status to indicate illegal name
                    $this->ErrorStatus = MetadataSchema::MDFSTAT_ILLEGALNAME;
                }
                else
                {
                    # lock DB tables to prevent next ID from being grabbed
                    $DB = $this->DB;
                    $DB->Query("
                        LOCK TABLES ".$ItemTableName." WRITE,
                        APSessions WRITE, APSessionData WRITE,
                        MetadataSchemas WRITE");

                    # get next temp item ID
                    $OldItemId = $this->Id();
                    $Factory = new $ItemFactoryObjectName();
                    if ($NewSetting == TRUE)
                    {
                        $NewId = $Factory->GetNextTempItemId();
                    }
                    else
                    {
                        $NewId = $Factory->GetNextItemId();
                    }

                    # change item ID
                    $DB->Query("UPDATE ".$ItemTableName." SET ".$ItemIdFieldName." = ".
                        $NewId.  " WHERE ".$ItemIdFieldName." = ".$OldItemId);

                    # release DB tables
                    $DB->Query("UNLOCK TABLES");

                    # change associations
                    foreach ($ItemAssociationTables as $TableName)
                    {
                        $DB->Query("UPDATE ".$TableName." SET ".$ItemAssociationFieldName." = ".
                                $NewId.  " WHERE ".$ItemAssociationFieldName." = ".$OldItemId);
                    }

                    # if changing item from temp to non-temp
                    if ($NewSetting == FALSE)
                    {
                        # add any needed database fields and/or entries
                        $this->AddDatabaseFields();

                        # set field order values for new field
                        $Schema->GetDisplayOrder()->AppendItem($NewId, "MetadataField");
                        $Schema->GetEditOrder()->AppendItem($NewId, "MetadataField");
                    }

                    # update metadata field id
                    $this->DBFields["FieldId"] = $NewId;
                }
            }
        }

        # report to caller whether we are a temp item
        return ($this->Id() < 0) ? TRUE : FALSE;
    }

    /**
    * Get/set privileges that allowing authoring values for this field.
    * @param object $NewValue New PrivilegeSet value.  (OPTIONAL)
    * @return object PrivilegeSet that allows authoring.
    */
    function AuthoringPrivileges(PrivilegeSet $NewValue = NULL)
    {
        # if new privileges supplied
        if ($NewValue !== NULL)
        {
            # store new privileges in database
            $this->UpdateValue("AuthoringPrivileges", $NewValue->Data());
            $this->AuthoringPrivileges = $NewValue;
        }

        # return current value to caller
        return $this->AuthoringPrivileges;
    }

    /**
    * Get/set privileges that allowing editing values for this field.
    * @param object $NewValue New PrivilegeSet value.  (OPTIONAL)
    * @return object PrivilegeSet that allows editing.
    */
    function EditingPrivileges(PrivilegeSet $NewValue = NULL)
    {
        # if new privileges supplied
        if ($NewValue !== NULL)
        {
            # store new privileges in database
            $this->UpdateValue("EditingPrivileges", $NewValue->Data());
            $this->EditingPrivileges = $NewValue;
        }

        # return current value to caller
        return $this->EditingPrivileges;
    }

    /**
    * Get/set privileges that allowing viewing values for this field.
    * @param object $NewValue New PrivilegeSet value.  (OPTIONAL)
    * @return object PrivilegeSet that allows viewing.
    */
    function ViewingPrivileges(PrivilegeSet $NewValue = NULL)
    {
        # if new privileges supplied
        if ($NewValue !== NULL)
        {
            # store new privileges in database
            $this->UpdateValue("ViewingPrivileges", $NewValue->Data());
            $this->ViewingPrivileges = $NewValue;
        }

        # return current value to caller
        return $this->ViewingPrivileges;
    }

    /**
    * Get/set privileges that allowing previewing values for this field.
    * @param object $NewValue New PrivilegeSet value.  (OPTIONAL)
    * @return object PrivilegeSet that allows previewing.
    */
    function PreviewingPrivileges(PrivilegeSet $NewValue = NULL)
    {
        # if new privileges supplied
        if ($NewValue !== NULL)
        {
            # store new privileges in database
            $this->UpdateValue("PreviewingPrivileges", $NewValue->Data());
            $this->PreviewingPrivileges = $NewValue;
        }

        # return current value to caller
        return $this->PreviewingPrivileges;
    }

    /**
    * Get metadata field ID.
    * @return int Field ID.
    */
    function Id() {  return $this->Id;  }

    /**
    * Get base name of database column used to store metadata field
    * value.  (Only valid for some field types.)
    * @return string Column name.
    */
    function DBFieldName() {  return $this->DBFields["DBFieldName"];  }

    /**
    * Get/set field description.
    * @param string New description.  (OPTIONAL)
    * @return string Current field description.
    */
    function Description($NewValue = DB_NOVALUE)
        {  return $this->UpdateValue("Description", $NewValue);  }

    /**
    * Get/set field instructions.
    * @param string New instructions.  (OPTIONAL)
    * @return string Current field instructions.
    */
    function Instructions($NewValue = DB_NOVALUE)
        {  return $this->UpdateValue("Instructions", $NewValue);  }

    /**
    * Get/set field owner.
    * @param string New owner.  (OPTIONAL)
    * @return string Current owner.
    */
    function Owner($NewValue = DB_NOVALUE)
        {  return $this->UpdateValue("Owner", $NewValue);  }

    /**
    * Get/set whether field is enabled.
    * @param bool $NewValue TRUE to enable field, or FALSE to disable.
    *       (OPTIONAL)
    * @return bool TRUE if field is enabled, otherwise FALSE.
    */
    function Enabled($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("Enabled", $NewValue);  }

    /**
    * Get/set whether a value is required for this field.
    * @param bool $NewValue TRUE to require a value, or FALSE to make
    *       entering a value optional.  (OPTIONAL)
    * @return bool TRUE if a value is required, otherwise FALSE.
    */
    function Optional($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("Optional", $NewValue);  }

    /**
    * Get/set whether this field is editable.
    * @param bool $NewValue TRUE to indicate that field is editable,
    *       or FALSE to indicate it non-editable.  (OPTIONAL)
    * @return bool TRUE if field is editable, otherwise FALSE.
    */
    function Editable($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("Editable", $NewValue);  }

    /**
    * Get/set whether to allow multiple values for field.
    * @param bool $NewValue TRUE to allow multiple values, or FALSE if
    *       only one value may be set.  (OPTIONAL)
    * @return bool TRUE if field allows multiple values, otherwise FALSE.
    */
    function AllowMultiple($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("AllowMultiple", $NewValue);  }

    /**
    * Get/set whether to include field in keyword search.
    * @param bool $NewValue TRUE to include field, or FALSE if field should
    *       not be included.  (OPTIONAL)
    * @return bool TRUE if field should be included, otherwise FALSE.
    */
    function IncludeInKeywordSearch($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("IncludeInKeywordSearch", $NewValue);  }

    /**
    * Get/set whether to include field in advanced search.
    * @param bool $NewValue TRUE to include field, or FALSE if field should
    *       not be included.  (OPTIONAL)
    * @return bool TRUE if field should be included, otherwise FALSE.
    */
    function IncludeInAdvancedSearch($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("IncludeInAdvancedSearch", $NewValue);  }

    /**
    * Get/set whether to include field in faceted search.
    * @param bool $NewValue TRUE to include field, or FALSE if field should
    *       not be included.  (OPTIONAL)
    * @return bool TRUE if field should be included, otherwise FALSE.
    */
    function IncludeInFacetedSearch($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("IncludeInFacetedSearch", $NewValue);  }

    /**
    * Get/set whether to include field in search result sort options.
    * @param bool $NewValue TRUE to include field, or FALSE if field should
    *       not be included.  (OPTIONAL)
    * @return bool TRUE if field should be included, otherwise FALSE.
    */
    function IncludeInSortOptions($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("IncludeInSortOptions", $NewValue);  }

    /**
    * Get/set whether to include field in recommender system comparisons.
    * @param bool $NewValue TRUE to include field, or FALSE if field should
    *       not be included.  (OPTIONAL)
    * @return bool TRUE if field should be included, otherwise FALSE.
    */
    function IncludeInRecommender($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("IncludeInRecommender", $NewValue);  }
    function TextFieldSize($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("TextFieldSize", $NewValue);  }
    function MaxLength($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("MaxLength", $NewValue);  }
    function ParagraphRows($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("ParagraphRows", $NewValue);  }
    function ParagraphCols($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("ParagraphCols", $NewValue);  }
    function MinValue($NewValue = DB_NOVALUE)
        {  return $this->UpdateFloatValue("MinValue", $NewValue);  }
    function MaxValue($NewValue = DB_NOVALUE)
        {  return $this->UpdateFloatValue("MaxValue", $NewValue);  }
    function FlagOnLabel($NewValue = DB_NOVALUE)
        {  return $this->UpdateValue("FlagOnLabel", $NewValue);  }
    function FlagOffLabel($NewValue = DB_NOVALUE)
        {  return $this->UpdateValue("FlagOffLabel", $NewValue);  }
    function DateFormat($NewValue = DB_NOVALUE)
        {  return $this->UpdateValue("DateFormat", $NewValue);  }
    function SearchWeight($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("SearchWeight", $NewValue);  }
    function RecommenderWeight($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("RecommenderWeight", $NewValue);  }
    function MaxHeight($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("MaxHeight", $NewValue);  }
    function MaxWidth($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("MaxWidth", $NewValue);  }
    function MaxPreviewHeight($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("MaxPreviewHeight", $NewValue);  }
    function MaxPreviewWidth($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("MaxPreviewWidth", $NewValue);  }
    function MaxThumbnailHeight($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("MaxThumbnailHeight", $NewValue);  }
    function MaxThumbnailWidth($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("MaxThumbnailWidth", $NewValue);  }
    function DefaultAltText($NewValue = DB_NOVALUE)
        {  return $this->UpdateValue("DefaultAltText", $NewValue);  }
    function UsesQualifiers($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("UsesQualifiers", $NewValue);  }
    function ShowQualifiers($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("ShowQualifiers", $NewValue);  }
    function DefaultQualifier($NewValue = DB_NOVALUE)
        {  return $this->UpdateValue("DefaultQualifier", $NewValue);  }
    function AllowHTML($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("AllowHTML", $NewValue);  }
    function UseWysiwygEditor($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("UseWysiwygEditor", $NewValue);  }
    function UseForOaiSets($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("UseForOaiSets", $NewValue);  }
    function OptionListThreshold($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("OptionListThreshold", $NewValue);  }
    function AjaxThreshold($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("AjaxThreshold", $NewValue);  }
    function NumAjaxResults($NewValue = DB_NOVALUE)
        {  return $this->UpdateIntValue("NumAjaxResults", $NewValue);  }
    function ViewingPrivilege($NewValue = DB_NOVALUE)
        {  return $this->UpdateConstValue("ViewingPrivilege", $NewValue);  }
    function AuthoringPrivilege($NewValue = DB_NOVALUE)
        {  return $this->UpdateConstValue("AuthoringPrivilege", $NewValue);  }
    function EditingPrivilege($NewValue = DB_NOVALUE)
        {  return $this->UpdateConstValue("EditingPrivilege", $NewValue);  }
    function ImagePreviewPrivilege($NewValue = DB_NOVALUE)
        {  return $this->UpdateConstValue("ImagePreviewPrivilege", $NewValue);  }
    function TreeBrowsingPrivilege($NewValue = DB_NOVALUE)
        {  return $this->UpdateConstValue("TreeBrowsingPrivilege", $NewValue);  }
    function EnableOnOwnerReturn($NewValue = DB_NOVALUE)
        {   return $this->UpdateBoolValue("EnableOnOwnerReturn", $NewValue);  }
    function ViewingUserIsValue($NewValue = DB_NOVALUE)
        {   return $this->UpdateConstValue("ViewingUserIsValue", $NewValue, "MetadataField");  }
    function AuthoringUserIsValue($NewValue = DB_NOVALUE)
        {   return $this->UpdateConstValue("AuthoringUserIsValue", $NewValue, "MetadataField");  }
    function EditingUserIsValue($NewValue = DB_NOVALUE)
        {   return $this->UpdateConstValue("EditingUserIsValue", $NewValue, "MetadataField");  }
    function ViewingUserValue($NewValue = DB_NOVALUE)
        {   return $this->UpdateIntValue("ViewingUserValue", $NewValue, "MetadataField");  }
    function AuthoringUserValue($NewValue = DB_NOVALUE)
        {   return $this->UpdateIntValue("AuthoringUserValue", $NewValue, "MetadataField");  }
    function EditingUserValue($NewValue = DB_NOVALUE)
        {   return $this->UpdateIntValue("EditingUserValue", $NewValue, "MetadataField");  }
    function RequiredBySPT($NewValue = DB_NOVALUE)
        {  return $this->UpdateBoolValue("RequiredBySPT", $NewValue);  }

    function UserPrivilegeRestrictions($NewValue = DB_NOVALUE)
    {
        # new value
        if ($NewValue != DB_NOVALUE)
        {
            $NewValue = serialize((array) $NewValue);
        }

        $Value = $this->UpdateValue("UserPrivilegeRestrictions", $NewValue);

        # value set
        if (strlen($Value))
        {
            $Value = (array) unserialize($Value);
        }

        # no value set, set it to an empty array
        else
        {
            $Value = $this->UserPrivilegeRestrictions(array());
        }

        return $Value;
    }

    function PointPrecision($NewValue = DB_NOVALUE)
    {
        if ($NewValue !== DB_NOVALUE && $this->Id() >= 0
            && $this->Type() == MetadataSchema::MDFTYPE_POINT)
        {
            $OldValue = $this->UpdateValue("PointPrecision", DB_NOVALUE);

            if ($NewValue != $OldValue)
            {
                $Decimals  = $this->UpdateValue("PointDecimalDigits", DB_NOVALUE);
                $TotalDigits = $NewValue + $Decimals;

                $this->DB->Query("ALTER TABLE Resources MODIFY COLUMN "
                           ."`".$this->DBFields["DBFieldName"]."X` "
                           ."DECIMAL(".$TotalDigits.",".$Decimals.")");
                $this->DB->Query("ALTER TABLE Resources MODIFY COLUMN "
                           ."`".$this->DBFields["DBFieldName"]."Y` "
                           ."DECIMAL(".$TotalDigits.",".$Decimals.")");
            }
        }

        return $this->UpdateValue("PointPrecision", $NewValue);
    }

    function PointDecimalDigits($NewValue = DB_NOVALUE)
    {
        if ($NewValue !== DB_NOVALUE && $this->Id() >= 0
            && $this->Type() == MetadataSchema::MDFTYPE_POINT)
        {
            $OldValue = $this->UpdateValue("PointDecimalDigits", DB_NOVALUE);

            if ($NewValue != $OldValue)
            {
                $Precision = $this->UpdateValue("PointPrecision", DB_NOVALUE);

                $TotalDigits = $NewValue + $Precision;

                $this->DB->Query("ALTER TABLE Resources MODIFY COLUMN "
                           ."`".$this->DBFields["DBFieldName"]."X` "
                           ."DECIMAL(".$TotalDigits.",".$NewValue.")");
                $this->DB->Query("ALTER TABLE Resources MODIFY COLUMN "
                           ."`".$this->DBFields["DBFieldName"]."Y` "
                           ."DECIMAL(".$TotalDigits.",".$NewValue.")");
            }
        }

        return $this->UpdateValue("PointDecimalDigits", $NewValue);
    }

    function DefaultValue($NewValue = DB_NOVALUE)
    {
        if ($this->Type() == MetadataSchema::MDFTYPE_POINT)
        {
            # valid value given
            if ($NewValue !== DB_NOVALUE &&
                isset($NewValue["X"]) && isset($NewValue["Y"]))
            {
                $NewValue = $NewValue["X"].",".$NewValue["Y"];
            }

            # invalid value given
            else
            {
                $NewValue = DB_NOVALUE;
            }

            $Value = $this->UpdateValue("DefaultValue", $NewValue);

            if (is_array($Value))
            {
                $tmp = explode(",", $Value);

                if (count($tmp)==2)
                {
                    return array("X" => $tmp[0], "Y" => $tmp[1]);
                }
            }

            return array("X" => NULL, "Y" => NULL);
        }

        else if ($this->Type() == MetadataSchema::MDFTYPE_OPTION)
        {
            # multiple default values to set
            if (is_array($NewValue))
            {
                # empty array
                if (count($NewValue) == 0)
                {
                    $NewValue = NULL;
                }

                # multiple defaults are allowed
                else if ($this->AllowMultiple())
                {
                    $NewValue = serialize($NewValue);
                }

                # only one default is allowed so get the first one
                else
                {
                    $NewValue = array_shift($NewValue);
                }
            }

            $Result = $this->UpdateValue("DefaultValue", $NewValue);

            return empty($Result) || is_numeric($Result) ?
                $Result : unserialize($Result);
        }

        return $this->UpdateValue("DefaultValue", $NewValue);
    }

    /**
     * Get/set method by which field is updated.
     * @param string $NewValue New update method.
     * @return Existing update method.
     */
    function UpdateMethod($NewValue = DB_NOVALUE)
    {
        return $this->UpdateValue("UpdateMethod", $NewValue);
    }

    # get possible values (only meaningful for Trees, Controlled Names, Options,
    # Flags, and Users)
    # (index for returned array is IDs for values)
    function GetPossibleValues($MaxNumberOfValues = NULL, $Offset=0)
    {
        # retrieve values based on field type
        switch ($this->Type())
        {
            case MetadataSchema::MDFTYPE_TREE:
                $QueryString = "SELECT ClassificationId, ClassificationName"
                        ." FROM Classifications WHERE FieldId = ".$this->Id()
                        ." ORDER BY ClassificationName";
                if ($MaxNumberOfValues)
                {
                    $QueryString .= " LIMIT ".intval($MaxNumberOfValues)." OFFSET "
                        .intval($Offset);
                }
                $this->DB->Query($QueryString);
                $PossibleValues = $this->DB->FetchColumn(
                        "ClassificationName", "ClassificationId");
                break;

            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
                $QueryString = "SELECT ControlledNameId, ControlledName"
                        ." FROM ControlledNames WHERE FieldId = ".$this->Id()
                        ." ORDER BY ControlledName";
                if ($MaxNumberOfValues)
                {
                    $QueryString .= " LIMIT ".intval($MaxNumberOfValues)." OFFSET "
                        .intval($Offset);
                }
                $this->DB->Query($QueryString);
                $PossibleValues = $this->DB->FetchColumn(
                        "ControlledName", "ControlledNameId");
                break;

            case MetadataSchema::MDFTYPE_FLAG:
                $PossibleValues[0] = $this->FlagOffLabel();
                $PossibleValues[1] = $this->FlagOnLabel();
                break;

            case MetadataSchema::MDFTYPE_USER:
                $UserFactory = new CWUserFactory();
                $Restrictions = $this->UserPrivilegeRestrictions();
                $PossibleValues = array();

                if (count($Restrictions))
                {
                    $PossibleValues = call_user_func_array(
                        array($UserFactory, "GetUsersWithPrivileges"),
                        $Restrictions);
                }

                else
                {
                    $Users = $UserFactory->GetMatchingUsers(".*.");

                    foreach ($Users as $Id => $Data)
                    {
                        $PossibleValues[$Id] = $Data["UserName"];
                    }
                }

                break;

            default:
                # for everything else return an empty array
                $PossibleValues = array();
                break;
        }

        # return array of possible values to caller
        return $PossibleValues;
    }

    # get count of possible values (only meaningful for Trees, Controlled Names,
    # Options, and Users)
    function GetCountOfPossibleValues()
    {
        # retrieve values based on field type
        switch ($this->Type())
        {
            case MetadataSchema::MDFTYPE_TREE:
                $Count = $this->DB->Query("SELECT count(*) AS ValueCount"
                        ." FROM Classifications WHERE FieldId = ".$this->Id(),
                        "ValueCount");
                break;

            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
                $Count = $this->DB->Query("SELECT count(*) AS ValueCount"
                        ." FROM ControlledNames WHERE FieldId = ".$this->Id(),
                        "ValueCount");
                break;

            case MetadataSchema::MDFTYPE_FLAG:
                $Count = 2;
                break;

            case MetadataSchema::MDFTYPE_USER:
                $Count = count($this->GetPossibleValues());
                break;

            default:
                # for everything else return an empty array
                $Count = 0;
                break;
        }

        # return count of possible values to caller
        return $Count;
    }

    # get ID for specified value (only meaningful for Trees / Controlled Names / Options)
    # (returns NULL if value not found)
    function GetIdForValue($Value)
    {
        # retrieve ID based on field type
        switch ($this->Type())
        {
            case MetadataSchema::MDFTYPE_TREE:
                $Id = $this->DB->Query("SELECT ClassificationId FROM Classifications"
                        ." WHERE ClassificationName = '".addslashes($Value)."'"
                        ." AND FieldId = ".$this->Id(),
                        "ClassificationId");
                break;

            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
                $Id = $this->DB->Query("SELECT ControlledNameId FROM ControlledNames"
                        ." WHERE ControlledName = '".addslashes($Value)."'"
                        ." AND FieldId = ".$this->Id(),
                        "ControlledNameId");
                break;

            default:
                # for everything else return NULL
                $Id = NULL;
                break;
        }

        # return ID for value to caller
        return $Id;
    }

    # get value for specified ID (only meaningful for Trees / Controlled Names / Options)
    # (returns NULL if ID not found)
    function GetValueForId($Id)
    {
        # retrieve ID based on field type
        switch ($this->Type())
        {
            case MetadataSchema::MDFTYPE_TREE:
                $Value = $this->DB->Query("SELECT ClassificationName FROM Classifications"
                        ." WHERE ClassificationId = '".intval($Id)."'"
                        ." AND FieldId = ".$this->Id(),
                        "ClassificationName");
                break;

            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
                $Value = $this->DB->Query("SELECT ControlledName FROM ControlledNames"
                        ." WHERE ControlledNameId = '".intval($Id)."'"
                        ." AND FieldId = ".$this->Id(),
                        "ControlledName");
                break;

            default:
                # for everything else return NULL
                $Value = NULL;
                break;
        }

        # return ID for value to caller
        return $Value;
    }

    /**
     * Check how many times a specific value is currently used for this field.
     * This method is not valid for Date fields.
     * @param mixed $Value Value to check.  For Flag, Tree, Option, Image, and
     *       Controlled Name fields this must be an ID or an appropriate object.
     *       For Point fields this must be an associative array with two values
     *       with "X" and "Y" indexes.  Date fields are not supported.  For other
     *       field types, the literal value to check should be passed in.
     * @return Number of times values is currently used.
     */
    function ValueUseCount($Value)
    {
        # retrieve ID if object passed in
        if (is_object($Value) && method_exists($Value, "Id"))
        {
            $Value = $Value->Id();
        }

        # check value based on field type
        $DBFieldName = $this->DBFields["DBFieldName"];
        switch ($this->Type())
        {
            case MetadataSchema::MDFTYPE_TEXT:
            case MetadataSchema::MDFTYPE_PARAGRAPH:
            case MetadataSchema::MDFTYPE_NUMBER:
            case MetadataSchema::MDFTYPE_USER:
            case MetadataSchema::MDFTYPE_IMAGE:
            case MetadataSchema::MDFTYPE_TIMESTAMP:
            case MetadataSchema::MDFTYPE_URL:
            case MetadataSchema::MDFTYPE_FLAG:
            case MetadataSchema::MDFTYPE_FILE:
                $UseCount = $this->DB->Query("SELECT COUNT(*) AS UseCount"
                        ." FROM Resources"
                        ." WHERE `".$DBFieldName."` = '".addslashes($Value)."'"
                        ." AND SchemaId = ".intval($this->DBFields["SchemaId"]),
                        "UseCount");
                break;

            case MetadataSchema::MDFTYPE_TREE:
                $UseCount = $this->DB->Query("SELECT COUNT(*) AS UseCount"
                        ." FROM ResourceClassInts"
                        ." WHERE ClassificationId = ".intval($Value),
                        "UseCount");
                break;

            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
                $UseCount = $this->DB->Query("SELECT COUNT(*) AS UseCount"
                        ." FROM ResourceNameInts"
                        ." WHERE ControlledNameId = ".intval($Value),
                        "UseCount");
                break;

            case MetadataSchema::MDFTYPE_POINT:
                $UseCount = $this->DB->Query("SELECT COUNT(*) AS UseCount"
                        ." FROM Resources"
                        ." WHERE `".$DBFieldName."X` = '".$Value["X"]."'"
                        ." AND `".$DBFieldName."Y` = '".$Value["Y"]."'"
                        ." AND SchemaId = ".intval($this->DBFields["SchemaId"]),
                        "UseCount");
                break;

            default:
                throw new Exception(__CLASS__."::".__METHOD__."() called for"
                        ." unsupported field type (".$this->Type().").");
                break;
        }

        # report use count to caller
        return $UseCount;
    }

    # get/set whether field uses item-level qualifiers
    function HasItemLevelQualifiers($NewValue = DB_NOVALUE)
    {
        # if value provided different from present value
        if (($NewValue != DB_NOVALUE)
            && ($NewValue != $this->DBFields["HasItemLevelQualifiers"]))
        {
            # check if qualifier column currently exists
            $QualColName = $this->DBFieldName()."Qualifier";
            $QualColExists = $this->DB->FieldExists("Resources", $QualColName);

            # if new value indicates qualifiers should now be used
            if ($NewValue == TRUE)
            {
                # if qualifier column does not exist in DB for this field
                if ($QualColExists == FALSE)
                {
                    # add qualifier column in DB for this field
                    $this->DB->Query("ALTER TABLE Resources ADD COLUMN `"
                                     .$QualColName."` INT");
                }
            }
            else
            {
                # if qualifier column exists in DB for this field
                if ($QualColExists == TRUE)
                {
                    # remove qualifier column from DB for this field
                    $this->DB->Query("ALTER TABLE Resources DROP COLUMN `"
                                     .$QualColName."`");
                }
            }
        }

        return $this->UpdateValue("HasItemLevelQualifiers", $NewValue);
    }

    # get list of qualifiers associated with field
    function AssociatedQualifierList()
    {
        # start with empty list
        $List = array();

        # for each associated qualifier
        $this->DB->Query("SELECT QualifierId FROM FieldQualifierInts"
                     ." WHERE MetadataFieldId = ".$this->DBFields["FieldId"]);
        while ($Record = $this->DB->FetchRow())
        {
            # load qualifier object
            $Qual = new Qualifier($Record["QualifierId"]);

            # add qualifier ID and name to list
            $List[$Qual->Id()] = $Qual->Name();
        }

        # return list to caller
        return $List;
    }

    # get list of qualifiers not associated with field
    function UnassociatedQualifierList()
    {
        # grab list of associated qualifiers
        $AssociatedQualifiers = $this->AssociatedQualifierList();

        # get list of all qualifiers
        $QFactory = new QualifierFactory();
        $AllQualifiers = $QFactory->GetItemNames();

        # return list of unassociated qualifiers
        return array_diff($AllQualifiers, $AssociatedQualifiers);
    }

    /**
    * Associate qualifier with field.
    * @param mixed $Qualifier Qualifer ID, name, or object.
    */
    function AddQualifier($Qualifier)
    {
        # if qualifier object passed in
        if (is_object($Qualifier))
        {
            # grab qualifier ID from object
            $Qualifier = $Qualifier->Id();
        }
        # else if string passed in does not look like ID
        elseif (!preg_match('/^[0-9]+$/', $Qualifier))
        {
            # assume string passed in is name and use it to retrieve ID
            $QFact = new QualifierFactory();
            $Qualifier = $QFact->GetItemIdByName($Qualifier);
        }

        # if not already associated
        $RecordCount = $this->DB->Query(
            "SELECT COUNT(*) AS RecordCount FROM FieldQualifierInts"
            ." WHERE QualifierId = ".$Qualifier
            ." AND MetadataFieldId = ".$this->Id(), "RecordCount");
        if ($RecordCount < 1)
        {
            # associate field with qualifier
            $this->DB->Query("INSERT INTO FieldQualifierInts SET"
                             ." QualifierId = ".$Qualifier.","
                             ." MetadataFieldId = ".$this->Id());
        }
    }
    /**
    * DEPRECATED METHOD
    * @param mixed $Qualifier
    * @see AddQualifier()
    */
    function AssociateWithQualifier($Qualifier)
            {  $this->AddQualifier($Qualifier);  }

    # delete qualifier association
    function UnassociateWithQualifier($QualifierIdOrObject)
    {
        # if qualifier object passed in
        if (is_object($QualifierIdOrObject))
        {
            # grab qualifier ID from object
            $QualifierIdOrObject = $QualifierIdOrObject->Id();
        }

        # delete intersection record from database
        $this->DB->Query("DELETE FROM FieldQualifierInts WHERE QualifierId = "
                         .$QualifierIdOrObject." AND MetadataFieldId = ".
                         $this->Id());
    }

    # retrieve item factory object for this field
    function GetFactory()
    {
        switch ($this->Type())
        {
            case MetadataSchema::MDFTYPE_TREE:
                $Factory = new ClassificationFactory($this->Id());
                break;

            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
                $Factory = new ControlledNameFactory($this->Id());
                break;

            default:
                $Factory = NULL;
                break;
        }

        return $Factory;
    }

    /**
    * This function has been deprecated and should not be used.
    * @deprecated
    */
    function Viewable()
    {
        # the field should not be viewed if it is disabled
        if (!$this->Enabled())
        {
            return FALSE;
        }

        $UserPrivs = $GLOBALS["G_User"]->Privileges();

        # the user can view the field if they can edit it
        if ($UserPrivs->IsGreaterThan($this->EditingPrivileges()))
        {
            return TRUE;
        }

        # if the user can view the field
        if ($UserPrivs->IsGreaterThan($this->ViewingPrivileges()))
        {
            return TRUE;
        }

        # the user can't view the field
        return FALSE;
    }

    # ---- PRIVATE INTERFACE -------------------------------------------------

    private $DB;
    private $Id;
    private $DBFields;
    private $ErrorStatus;
    private $AuthoringPrivileges;
    private $EditingPrivileges;
    private $ViewingPrivileges;
    private $PreviewingPrivileges;

    /**
     * A map of metadata field types to human-readable strings.
     * @var array $FieldTypeHumanEnums
     */
    public static $FieldTypeHumanEnums = array(
            MetadataSchema::MDFTYPE_TEXT             => "Text",
            MetadataSchema::MDFTYPE_PARAGRAPH        => "Paragraph",
            MetadataSchema::MDFTYPE_NUMBER           => "Number",
            MetadataSchema::MDFTYPE_DATE             => "Date",
            MetadataSchema::MDFTYPE_TIMESTAMP        => "Timestamp",
            MetadataSchema::MDFTYPE_FLAG             => "Flag",
            MetadataSchema::MDFTYPE_TREE             => "Tree",
            MetadataSchema::MDFTYPE_CONTROLLEDNAME   => "Controlled Name",
            MetadataSchema::MDFTYPE_OPTION           => "Option",
            MetadataSchema::MDFTYPE_USER             => "User",
            MetadataSchema::MDFTYPE_IMAGE            => "Image",
            MetadataSchema::MDFTYPE_FILE             => "File",
            MetadataSchema::MDFTYPE_URL              => "URL",
            MetadataSchema::MDFTYPE_POINT            => "Point",
            MetadataSchema::MDFTYPE_REFERENCE        => "Reference");

    # field type DB/PHP enum translations
    public static $FieldTypeDBEnums = array(
            MetadataSchema::MDFTYPE_TEXT             => "Text",
            MetadataSchema::MDFTYPE_PARAGRAPH        => "Paragraph",
            MetadataSchema::MDFTYPE_NUMBER           => "Number",
            MetadataSchema::MDFTYPE_DATE             => "Date",
            MetadataSchema::MDFTYPE_TIMESTAMP        => "TimeStamp",
            MetadataSchema::MDFTYPE_FLAG             => "Flag",
            MetadataSchema::MDFTYPE_TREE             => "Tree",
            MetadataSchema::MDFTYPE_CONTROLLEDNAME   => "ControlledName",
            MetadataSchema::MDFTYPE_OPTION           => "Option",
            MetadataSchema::MDFTYPE_USER             => "User",
            MetadataSchema::MDFTYPE_IMAGE            => "Still Image",
            MetadataSchema::MDFTYPE_FILE             => "File",
            MetadataSchema::MDFTYPE_URL              => "Url",
            MetadataSchema::MDFTYPE_POINT            => "Point",
            MetadataSchema::MDFTYPE_REFERENCE        => "Reference"
            );
    public static $FieldTypeDBAllowedEnums = array(
            MetadataSchema::MDFTYPE_TEXT             => "Text",
            MetadataSchema::MDFTYPE_PARAGRAPH        => "Paragraph",
            MetadataSchema::MDFTYPE_NUMBER           => "Number",
            MetadataSchema::MDFTYPE_DATE             => "Date",
            MetadataSchema::MDFTYPE_TIMESTAMP        => "TimeStamp",
            MetadataSchema::MDFTYPE_FLAG             => "Flag",
            MetadataSchema::MDFTYPE_TREE             => "Tree",
            MetadataSchema::MDFTYPE_CONTROLLEDNAME   => "ControlledName",
            MetadataSchema::MDFTYPE_OPTION           => "Option",
            MetadataSchema::MDFTYPE_USER             => "User",
            MetadataSchema::MDFTYPE_IMAGE            => "Still Image",
            MetadataSchema::MDFTYPE_FILE             => "File",
            MetadataSchema::MDFTYPE_URL              => "Url",
            MetadataSchema::MDFTYPE_POINT            => "Point",
            MetadataSchema::MDFTYPE_REFERENCE        => "Reference"
            );
    public static $FieldTypePHPEnums = array(
            "Text"                   => MetadataSchema::MDFTYPE_TEXT,
            "Paragraph"              => MetadataSchema::MDFTYPE_PARAGRAPH,
            "Number"                 => MetadataSchema::MDFTYPE_NUMBER,
            "Date"                   => MetadataSchema::MDFTYPE_DATE,
            "TimeStamp"              => MetadataSchema::MDFTYPE_TIMESTAMP,
            "Flag"                   => MetadataSchema::MDFTYPE_FLAG,
            "Tree"                   => MetadataSchema::MDFTYPE_TREE,
            "ControlledName"         => MetadataSchema::MDFTYPE_CONTROLLEDNAME,
            "Option"                 => MetadataSchema::MDFTYPE_OPTION,
            "User"                   => MetadataSchema::MDFTYPE_USER,
            "Still Image"            => MetadataSchema::MDFTYPE_IMAGE,
            "File"                   => MetadataSchema::MDFTYPE_FILE,
            "Url"                    => MetadataSchema::MDFTYPE_URL,
            "Point"                  => MetadataSchema::MDFTYPE_POINT,
            "Reference"              => MetadataSchema::MDFTYPE_REFERENCE
            );

    public static $UpdateTypes = array(
        MetadataField::UPDATEMETHOD_NOAUTOUPDATE   => "Do not update automatically",
        MetadataField::UPDATEMETHOD_ONRECORDCREATE => "Update on record creation",
        MetadataField::UPDATEMETHOD_BUTTON         => "Provide an update button",
        MetadataField::UPDATEMETHOD_ONRECORDEDIT   => "Update when record is edited",
        MetadataField::UPDATEMETHOD_ONRECORDCHANGE => "Update when record is changed"
        );

    /**
    * Create a new metadata field.
    * @param int $SchemaId ID of schema in which to place field.
    * @param enum $FieldType Metadata field type.
    * @param string $FieldName Name of metadata field.
    * @param bool $Optional If FALSE, field must always have a value.
    *       (OPTIONAL, defaults to TRUE)
    * @param mixed $DefaultValue Default value for field.
    * @return object New MetadataField object.
    * @throws InvalidArgumentException if field type is invalid.
    * @throws InvalidArgumentException if field name is duplicates name of
    *       another existing field.
    */
    static function Create($SchemaId, $FieldType, $FieldName,
            $Optional = NULL, $DefaultValue = NULL)
    {
        # error out if field type is bad
        if (empty(MetadataField::$FieldTypeDBEnums[$FieldType]))
        {
            throw new InvalidArgumentException("Bad field type (".$FieldType.").");
        }

        # error out if field name is duplicate
        $DB = new Database();
        $FieldName = trim($FieldName);
        $DuplicateCount = $DB->Query(
                "SELECT COUNT(*) AS RecordCount FROM MetadataFields"
                        ." WHERE FieldName = '".addslashes($FieldName)."'"
                        ." AND SchemaId = ".intval($SchemaId),
                "RecordCount");
        if ($DuplicateCount > 0)
        {
            throw new InvalidArgumentException("Duplicate field name (".$FieldName.").");
        }

        # grab current user ID
        $UserId = $GLOBALS["G_User"]->Get("UserId");

        # normalize schema ID
        $Schema = new MetadataSchema($SchemaId);
        $SchemaId = $Schema->Id();

        # use schema privileges as starting privilege values
        $AuthorPrivs = $Schema->AuthoringPrivileges();
        $EditPrivs = $Schema->EditingPrivileges();
        $ViewPrivs = $Schema->ViewingPrivileges();
        $PreviewPrivs = $Schema->ViewingPrivileges();

        # lock DB tables and get next temporary field ID
        $DB->Query("LOCK TABLES MetadataFields WRITE");
        $FieldId = $Schema->GetNextTempItemId();

        # add field to MDF table in database
        $DB->Query("INSERT INTO MetadataFields"
                ." (FieldId, SchemaId, FieldName, FieldType, LastModifiedById,"
                        ." Optional, AuthoringPrivileges, EditingPrivileges,"
                        ." ViewingPrivileges, PreviewingPrivileges)"
                ." VALUES ("
                .intval($FieldId).", "
                .intval($SchemaId).","
                ." '".addslashes($FieldName)."',"
                ." '".MetadataField::$FieldTypeDBEnums[$FieldType]."', "
                .intval($UserId).", "
                .($Optional ? "1" : "0").","
                ."'".mysql_escape_string($AuthorPrivs->Data())."',"
                ."'".mysql_escape_string($EditPrivs->Data())."',"
                ."'".mysql_escape_string($ViewPrivs->Data())."',"
                ."'".mysql_escape_string($PreviewPrivs->Data())."')");

        # release DB tables
        $DB->Query("UNLOCK TABLES");

        # load field object
        $Field = new MetadataField($FieldId);

        # set field defaults
        $Field->SetDefaults();

        # set the default value if specified
        if ($DefaultValue !== NULL)
        {
            $Field->DefaultValue($DefaultValue);
        }

        # return newly-constructed field to caller
        return $Field;
    }

    /**
    * Object contstructor, used to load an existing metadata field.  To create
    * new fields, use
    * @param int $FieldId ID of metadata field to load.
    * @return object New MetadataField object.
    */
    function __construct($FieldId)
    {
        # assume everything will be okay
        $this->ErrorStatus = MetadataSchema::MDFSTAT_OK;

        # retrieve field info from database
        $this->DB = new Database();
        $Result = $this->DB->Query("SELECT * FROM MetadataFields"
                ." WHERE FieldId = ".intval($FieldId));
        if ($this->DB->NumRowsSelected() == 0)
        {
            throw new InvalidArgumentException("Invalid metadata field ID ("
                    .$FieldId.")");
        }
        $Row = $this->DB->FetchRow();
        $this->DBFields = $Row;
        $this->Id = $FieldId;

        # if privileges have not yet been initialized
        if (!strlen($this->DBFields["AuthoringPrivileges"]))
        {
            # set default values for privileges from metadata schema
            $Schema = new MetadataSchema($Row["SchemaId"]);
            $this->AuthoringPrivileges($Schema->AuthoringPrivileges());
            $this->EditingPrivileges($Schema->EditingPrivileges());
            $this->ViewingPrivileges($Schema->ViewingPrivileges());
            $this->PreviewingPrivileges($Schema->ViewingPrivileges());
        }
        else
        {
            # set privileges from stored values
            $this->AuthoringPrivileges = new PrivilegeSet(
                    $Row["AuthoringPrivileges"]);
            $this->EditingPrivileges = new PrivilegeSet(
                    $Row["EditingPrivileges"]);
            $this->ViewingPrivileges = new PrivilegeSet(
                    $Row["ViewingPrivileges"]);
            $this->PreviewingPrivileges = new PrivilegeSet(
                    $Row["PreviewingPrivileges"]);
        }

        # set database column name
        $this->DBFields["DBFieldName"] =
                $this->NormalizeFieldNameForDB($this->DBFields["FieldName"]);
    }

    /**
    * The metadata field defaults that are the same for all field types.
    * @var array $FixedDefaults
    */
    public static $FixedDefaults = array(
        "Label" => NULL,
        "Description" => NULL,
        "Instructions" => NULL,
        "Enabled" => TRUE,
        "Optional" => TRUE,
        "Editable" => TRUE,
        "AllowMultiple" => FALSE,
        "IncludeInKeywordSearch" => FALSE,
        "IncludeInAdvancedSearch" => FALSE,
        "IncludeInFacetedSearch" => FALSE,
        "IncludeInSortOptions" => TRUE,
        "IncludeInRecommender" => FALSE,
        "ParagraphRows" => 4,
        "ParagraphCols" => 50,
        "MinValue" => 1,
        "FlagOnLabel" => "On",
        "FlagOffLabel" => "Off",
        "DateFormat" => NULL,
        "RecommenderWeight" => 1,
        "MaxHeight" => 500,
        "MaxWidth" => 500,
        "MaxPreviewHeight" => 100,
        "MaxPreviewWidth" => 100,
        "MaxThumbnailHeight" => 50,
        "MaxThumbnailWidth" => 50,
        "DefaultAltText" => NULL,
        "UsesQualifiers" => FALSE,
        "HasItemLevelQualifiers" => FALSE,
        "ShowQualifiers" => FALSE,
        "DefaultQualifier" => NULL,
        "AllowHTML" => FALSE,
        "UseWysiwygEditor" => FALSE,
        "UseForOaiSets" => FALSE,
        "OptionListThreshold" => 25,
        "AjaxThreshold" => 50,
        "NumAjaxResults" => 50,
        "ViewingPrivilege" => NULL,
        "AuthoringPrivilege" => PRIV_MYRESOURCEADMIN,
        "EditingPrivilege" => PRIV_RESOURCEADMIN,
        "ImagePreviewPrivilege" => NULL,
        "TreeBrowsingPrivilege" => NULL,
        "ViewingUserIsValue" => self::USERISVALUE_UNSET,
        "AuthoringUserIsValue" => self::USERISVALUE_UNSET,
        "EditingUserIsValue" => self::USERISVALUE_UNSET,
        "ViewingUserValue" => NULL,
        "AuthoringUserValue" => NULL,
        "EditingUserValue" => NULL,
        "PointPrecision" => 8,
        "PointDecimalDigits" => 5,
        "UserPrivilegeRestrictions" => array(),
        "UpdateMethod" => "NoAutoUpdate",
        # 9999 is the default max value because default number field length is 4
        "MaxValue" => 9999);

    /**
     * The metadata field defaults that vary depending on the field type.
     * @var array $TypeBasedDefaults
     */
    public static $TypeBasedDefaults = array(
        MetadataSchema::MDFTYPE_TEXT  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_PARAGRAPH  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_NUMBER  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 4,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_DATE  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 10,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_TIMESTAMP  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_FLAG  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_TREE  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_CONTROLLEDNAME  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 3,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_OPTION  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 3,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_USER  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_IMAGE  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_FILE  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_URL  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 255),
        MetadataSchema::MDFTYPE_POINT  => array(
            "DefaultValue" => array("X" => NULL, "Y" => NULL),
            "SearchWeight" => 1,
            "TextFieldSize" => 10,
            "MaxLength" => 100),
        MetadataSchema::MDFTYPE_REFERENCE  => array(
            "DefaultValue" => NULL,
            "SearchWeight" => 1,
            "TextFieldSize" => 50,
            "MaxLength" => 100));

    /**
     * Set defaults values for the field.
     * @return void
     */
    public function SetDefaults()
    {
        # set defaults that are the same for every field
        foreach (self::$FixedDefaults as $Key => $Value)
        {
            $this->$Key($Value);
        }

        # set defaults that depend on the type of the field
        foreach (self::$TypeBasedDefaults[$this->Type()] as $Key => $Value)
        {
            $this->$Key($Value);
        }

        # tweak the update method if dealing with the date of record creation
        if ($this->Name() == "Date Of Record Creation")
        {
            $this->UpdateMethod("OnRecordCreate");
        }
    }

    # remove field from database (only for use by MetadataSchema object)
    function Drop()
    {
        # clear other database entries as appropriate for field type
        $DB = $this->DB;
        $DBFieldName = $this->DBFields["DBFieldName"];
        $Schema = new MetadataSchema($this->SchemaId());
        switch (MetadataField::$FieldTypePHPEnums[$this->DBFields["FieldType"]])
        {
            case MetadataSchema::MDFTYPE_TEXT:
            case MetadataSchema::MDFTYPE_PARAGRAPH:
            case MetadataSchema::MDFTYPE_NUMBER:
            case MetadataSchema::MDFTYPE_USER:
            case MetadataSchema::MDFTYPE_IMAGE:
            case MetadataSchema::MDFTYPE_TIMESTAMP:
            case MetadataSchema::MDFTYPE_URL:
            case MetadataSchema::MDFTYPE_FLAG:
                # remove field from resources table
                if ($DB->FieldExists("Resources", $DBFieldName))
                {
                    $DB->Query("ALTER TABLE Resources DROP COLUMN `".$DBFieldName."`");
                }
                break;

            case MetadataSchema::MDFTYPE_POINT:
                if ($DB->FieldExists("Resources", $DBFieldName."X"))
                {
                    $DB->Query("ALTER TABLE Resources DROP COLUMN `".$DBFieldName."X`");
                    $DB->Query("ALTER TABLE Resources DROP COLUMN `".$DBFieldName."Y`");
                }
                break;

            case MetadataSchema::MDFTYPE_DATE:
                # remove fields from resources table
                if ($DB->FieldExists("Resources", $DBFieldName."Begin"))
                {
                    $DB->Query("ALTER TABLE Resources DROP COLUMN `".$DBFieldName."Begin`");
                    $DB->Query("ALTER TABLE Resources DROP COLUMN `".$DBFieldName."End`");
                    $DB->Query("ALTER TABLE Resources DROP COLUMN `".$DBFieldName."Precision`");
                }
                break;

            case MetadataSchema::MDFTYPE_TREE:
                $DB->Query("SELECT ClassificationId FROM Classifications "
                           ."WHERE FieldId = ".$this->Id());
                $TempDB = new Database();
                while ($ClassificationId = $DB->FetchField("ClassificationId"))
                {
                    # remove any resource / name intersections
                    $TempDB->Query("DELETE FROM ResourceClassInts WHERE "
                                   ."ClassificationId = ".$ClassificationId);

                    # remove controlled name
                    $TempDB->Query("DELETE FROM Classifications WHERE "
                                   ."ClassificationId = ".$ClassificationId);
                }
                break;

            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
                $DB->Query("SELECT ControlledNameId FROM ControlledNames "
                           ."WHERE FieldId = ".$this->Id());
                $TempDB = new Database();
                while ($ControlledNameId = $DB->FetchField("ControlledNameId"))
                {
                    # remove any resource / name intersections
                    $TempDB->Query("DELETE FROM ResourceNameInts WHERE "
                                   ."ControlledNameId = ".$ControlledNameId);

                    # remove any variant names
                    $TempDB->Query("DELETE FROM VariantNames WHERE "
                                   ."ControlledNameId = ".$ControlledNameId);

                    # remove controlled name
                    $TempDB->Query("DELETE FROM ControlledNames WHERE "
                                   ."ControlledNameId = ".$ControlledNameId);
                }
                break;

            case MetadataSchema::MDFTYPE_FILE:
                # for each file associated with this field
                $DB->Query("SELECT FileId FROM Files WHERE FieldId = '".$this->Id()."'");
                while ($FileId = $DB->FetchRow())
                {
                    # delete file
                    $File = new File(intval($FileId));
                    $File->Delete();
                }
                break;

            case MetadataSchema::MDFTYPE_REFERENCE:
                # remove any resource references for the field
                $DB->Query("
                    DELETE FROM ReferenceInts
                    WHERE FieldId = '".addslashes($this->Id())."'");
                break;
        }

        # remove field from database
        $DB->Query("DELETE FROM MetadataFields "
                   ."WHERE FieldId = '".$this->DBFields["FieldId"]."'");

        # remove any qualifier associations
        $DB->Query("DELETE FROM FieldQualifierInts WHERE MetadataFieldId = '"
                   .$this->DBFields["FieldId"]."'");

        # get the order objects the field is part of
        foreach (MetadataFieldOrder::GetOrdersForSchema($Schema) as $Order)
        {
            # remove it if it's a direct descendant
            $Order->RemoveItem($this->Id(), "MetadataField");

            # also make sure to remove it if it's part of a group
            foreach ($Order->GetItemIds() as $Item)
            {
                if ($Item["Type"] == "MetadataFieldGroup")
                {
                    $Group = new MetadataFieldGroup($Item["ID"]);
                    $Group->RemoveItem($this->Id(), "MetadataField");
                }
            }
        }
    }

    # modify any database fields
    private function ModifyField($NewName = NULL, $NewType = NULL)
    {
        # grab old DB field name
        $OldDBFieldName = $this->DBFields["DBFieldName"];
        $OldFieldType = NULL;

        # if new field name supplied
        if ($NewName != NULL)
        {
            # cache the old name for options and controllednames below
            $OldName = $this->DBFields["FieldName"];

            # store new name
            $this->UpdateValue("FieldName", $NewName);

            # determine new DB field name
            $NewDBFieldName = $this->NormalizeFieldNameForDB($NewName);

            # store new database field name
            $this->DBFields["DBFieldName"] = $NewDBFieldName;
        }
        else
        {
            # set new field name equal to old field name
            $NewDBFieldName = $OldDBFieldName;
        }

        # if new type supplied
        if ($NewType != NULL)
        {
            # grab old field type
            $OldFieldType = MetadataField::$FieldTypePHPEnums[$this->DBFields["FieldType"]];

            # store new field type
            $this->UpdateValue("FieldType", MetadataField::$FieldTypeDBEnums[$NewType]);
        }

        # if this is not a temporary field
        if ($this->Id() >= 0)
        {
            # modify field in DB as appropriate for field type
            $DB = $this->DB;
            $FieldType = MetadataField::$FieldTypePHPEnums[$this->DBFields["FieldType"]];
            switch ($FieldType)
            {
                case MetadataSchema::MDFTYPE_TEXT:
                case MetadataSchema::MDFTYPE_PARAGRAPH:
                case MetadataSchema::MDFTYPE_URL:
                    # alter field declaration in Resources table
                    $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                            .$OldDBFieldName."` `"
                            .$NewDBFieldName."` TEXT "
                            .($this->DBFields["Optional"] ? "" : "NOT NULL"));
                    break;

                case MetadataSchema::MDFTYPE_NUMBER:
                case MetadataSchema::MDFTYPE_USER:
                    # alter field declaration in Resources table
                    $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                            .$OldDBFieldName."` `"
                            .$NewDBFieldName."` INT "
                            .($this->DBFields["Optional"] ? "" : "NOT NULL"));
                    break;

                case MetadataSchema::MDFTYPE_POINT:
                    $Precision = $this->UpdateValue("PointPrecision",
                                                    DB_NOVALUE);
                    $Digits    = $this->UpdateValue("PointDecimalDigits",
                                                    DB_NOVALUE);
                    $DB->Query("ALTER TABLE Resources CHANGE COLUMN "
                               ."`".$OldDBFieldName."X` "
                               ."`".$NewDBFieldName."X`".
                               " DECIMAL(".$Precision.",".$Digits.")");
                    $DB->Query("ALTER TABLE Resources CHANGE COLUMN "
                               ."`".$OldDBFieldName."Y` "
                               ."`".$NewDBFieldName."Y`".
                               " DECIMAL(".$Precision.",".$Digits.")");
                    break;

                case MetadataSchema::MDFTYPE_FILE:
                    # if DB field name has changed
                    if ($NewDBFieldName != $OldDBFieldName)
                    {
                        # alter field declaration in Resources table
                        $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                                   .$OldDBFieldName."` `"
                                   .$NewDBFieldName."` TEXT");
                    }
                    break;

                case MetadataSchema::MDFTYPE_FLAG:
                    # alter field declaration in Resources table
                    $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                               .$OldDBFieldName."` `"
                               .$NewDBFieldName."` INT"
                               ." DEFAULT ".intval($this->DefaultValue()));

                    # set any unset values to default
                    $DB->Query("UPDATE Resources SET `".$NewDBFieldName
                            ."` = ".intval($this->DefaultValue())
                            ." WHERE `".$NewDBFieldName."` IS NULL");
                    break;

                case MetadataSchema::MDFTYPE_DATE:
                    # if new type supplied and new type is different from old
                    if (($NewType != NULL) && ($NewType != $OldFieldType))
                    {
                        # if old type was time stamp
                        if ($OldFieldType == MetadataSchema::MDFTYPE_TIMESTAMP)
                        {
                            # change time stamp field in resources table to begin date
                            $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                                       .$OldDBFieldName."` `"
                                       .$NewDBFieldName."Begin` DATE "
                                       .($this->DBFields["Optional"] ? "" : "NOT NULL"));

                            # add end date and precision fields
                            $DB->Query("ALTER TABLE Resources ADD COLUMN `".$NewDBFieldName."End"
                                       ."` DATE");
                            $DB->Query("ALTER TABLE Resources ADD COLUMN `".$NewDBFieldName."Precision`"
                                       ." INT ".($Optional ? "" : "NOT NULL"));

                            # set precision to reflect time stamp content
                            $DB->Query("UPDATE Resources SET `".$NewDBFieldName."Precision` = "
                                       .(DATEPRE_BEGINYEAR|DATEPRE_BEGINMONTH|DATEPRE_BEGINDAY));
                        }
                        else
                        {
                            exit("<br>ERROR:  Attempt to convert metadata field to date from type other than timestamp<br>\n");
                        }
                    }
                    else
                    {
                        # change name of fields
                        $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                                   .$OldDBFieldName."Begin` `"
                                   .$NewDBFieldName."Begin` DATE "
                                   .($this->DBFields["Optional"] ? "" : "NOT NULL"));
                        $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                                   .$OldDBFieldName."End` `"
                                   .$NewDBFieldName."End` DATE "
                                   .($this->DBFields["Optional"] ? "" : "NOT NULL"));
                        $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                                   .$OldDBFieldName."Precision` `"
                                   .$NewDBFieldName."Precision` INT "
                                   .($this->DBFields["Optional"] ? "" : "NOT NULL"));
                    }
                    break;

                case MetadataSchema::MDFTYPE_TIMESTAMP:
                    # if new type supplied and new type is different from old
                    if (($NewType != NULL) && ($NewType != $OldFieldType))
                    {
                        # if old type was date
                        if ($OldFieldType == MetadataSchema::MDFTYPE_DATE)
                        {
                            # change begin date field in resource table to time stamp
                            $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                                       .$OldDBFieldName."Begin` `"
                                       .$NewDBFieldName."` DATETIME "
                                       .($this->DBFields["Optional"] ? "" : "NOT NULL"));

                            # drop end date and precision fields
                            $DB->Query("ALTER TABLE Resources DROP COLUMN `"
                                       .$OldDBFieldName."End`");
                            $DB->Query("ALTER TABLE Resources DROP COLUMN `"
                                       .$OldDBFieldName."Precision`");
                        }
                        else
                        {
                            exit("<br>ERROR:  Attempt to convert metadata field to time stamp from type other than date<br>\n");
                        }
                    }
                    else
                    {
                        # change name of field
                        $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                                   .$OldDBFieldName."` `"
                                   .$NewDBFieldName."` DATETIME "
                                   .($this->DBFields["Optional"] ? "" : "NOT NULL"));
                    }
                    break;

                case MetadataSchema::MDFTYPE_TREE:
                case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
                case MetadataSchema::MDFTYPE_OPTION:
                case MetadataSchema::MDFTYPE_REFERENCE:
                case MetadataSchema::MDFTYPE_IMAGE:
                    break;
            }

            # if qualifier DB field exists
            if ($DB->FieldExists("Resources", $OldDBFieldName."Qualifier"))
            {
                # rename qualifier DB field
                $DB->Query("ALTER TABLE Resources CHANGE COLUMN `"
                           .$OldDBFieldName."Qualifier` `"
                           .$NewDBFieldName."Qualifier` INT ");
            }
        }
    }

    # convenience functions to supply parameters to Database->UpdateValue()
    private function UpdateValue($FieldName, $NewValue)
    {
        return $this->DB->UpdateValue("MetadataFields", $FieldName, $NewValue,
                               "FieldId = ".intval($this->DBFields["FieldId"]),
                               $this->DBFields);
    }
    private function UpdateIntValue($FieldName, $NewValue)
    {
        return $this->DB->UpdateIntValue("MetadataFields", $FieldName, $NewValue,
                               "FieldId = ".intval($this->DBFields["FieldId"]),
                               $this->DBFields);
    }
    private function UpdateFloatValue($FieldName, $NewValue)
    {
        return $this->DB->UpdateFloatValue("MetadataFields", $FieldName, $NewValue,
                               "FieldId = ".intval($this->DBFields["FieldId"]),
                               $this->DBFields);
    }
    private function UpdateBoolValue($FieldName, $NewValue)
    {
        $NewValue = $this->TranslateStringToConstants($NewValue);
        return $this->DB->UpdateIntValue("MetadataFields", $FieldName, $NewValue,
                               "FieldId = ".intval($this->DBFields["FieldId"]),
                               $this->DBFields);
    }
    private function UpdateConstValue($FieldName, $NewValue, $ClassName=NULL)
    {
        $NewValue = $this->TranslateStringToConstants($NewValue, $ClassName);
        return $this->DB->UpdateIntValue("MetadataFields", $FieldName, $NewValue,
                               "FieldId = ".intval($this->DBFields["FieldId"]),
                               $this->DBFields);
    }

    # normalize field name for use as database field name
    private function NormalizeFieldNameForDB($Name)
    {
        return preg_replace("/[^a-z0-9]/i", "", $Name)
                .(($this->SchemaId() != MetadataSchema::SCHEMAID_DEFAULT)
                        ? $this->SchemaId() : "");
    }

    # add any needed database fields and/or entries
    private function AddDatabaseFields()
    {
        # grab values for common use
        $DB = $this->DB;
        $FieldName = $this->Name();
        $DBFieldName = $this->DBFieldName();
        $Optional = $this->Optional();
        $DefaultValue = $this->DefaultValue();

        # set up field(s) based on field type
        switch ($this->Type())
        {
            case MetadataSchema::MDFTYPE_TEXT:
            case MetadataSchema::MDFTYPE_PARAGRAPH:
            case MetadataSchema::MDFTYPE_URL:
                # add field to resources table (if not already present)
                if (!$DB->FieldExists("Resources", $DBFieldName))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `".$DBFieldName
                               ."` TEXT ".($Optional ? "" : "NOT NULL"));
                }

                # if default value supplied
                if ($DefaultValue != NULL)
                {
                    # set all existing records to default value
                    $DB->Query("UPDATE Resources SET `"
                               .$DBFieldName."` = '".addslashes($DefaultValue)."'");
                }
                break;

            case MetadataSchema::MDFTYPE_NUMBER:
                # add field to resources table (if not already present)
                if (!$DB->FieldExists("Resources", $DBFieldName))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `".$DBFieldName
                               ."` INT ".($Optional ? "" : "NOT NULL"));
                }

                # if default value supplied
                if ($DefaultValue != NULL)
                {
                    # set all existing records to default value
                    $DB->Query("UPDATE Resources SET `"
                               .$DBFieldName."` = '".addslashes($DefaultValue)."'");
                }
                break;

            case MetadataSchema::MDFTYPE_POINT:
                if (!$DB->FieldExists("Resources", $DBFieldName."X"))
                {
                    $Precision = $this->UpdateValue("PointPrecision",
                                                    DB_NOVALUE);
                    $Digits    = $this->UpdateValue("PointDecimalDigits",
                                                    DB_NOVALUE);

                    $DB->Query("ALTER TABLE Resources ADD COLUMN `"
                               .$DBFieldName."X`".
                               " DECIMAL(".$Precision.",".$Digits.")");
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `"
                               .$DBFieldName."Y`".
                               " DECIMAL(".$Precision.",".$Digits.")");
                }

                break;
            case MetadataSchema::MDFTYPE_FLAG:
                # if field is not already present in database
                if (!$DB->FieldExists("Resources", $DBFieldName))
                {
                    # add field to resources table
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `".$DBFieldName
                               ."` INT DEFAULT ".intval($DefaultValue));

                    # set all existing records to default value
                    $DB->Query("UPDATE Resources SET `"
                               .$DBFieldName."` = ".intval($DefaultValue));
                }
                break;

            case MetadataSchema::MDFTYPE_USER:
                # add field to resources table (if not already present)
                if (!$DB->FieldExists("Resources", $DBFieldName))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `".$DBFieldName
                               ."` INT ".($Optional ? "" : "NOT NULL"));
                }
                break;

            case MetadataSchema::MDFTYPE_FILE:
                # add fields to resources table (if not already present)
                if (!$DB->FieldExists("Resources", $DBFieldName))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `"
                               .$DBFieldName."` TEXT");
                }
                break;

            case MetadataSchema::MDFTYPE_IMAGE:
                # add fields to resources table (if not already present)
                if (!$DB->FieldExists("Resources", $DBFieldName))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `"
                               .$DBFieldName."` INT");
                }
                break;

            case MetadataSchema::MDFTYPE_DATE:
                # add fields to resources table (if not already present)
                if (!$DB->FieldExists("Resources", $DBFieldName."Begin"))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `".$DBFieldName."Begin`"
                               ." DATE ".($Optional ? "" : "NOT NULL"));
                }
                if (!$DB->FieldExists("Resources", $DBFieldName."End"))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `".$DBFieldName."End`"
                               ." DATE");
                }
                if (!$DB->FieldExists("Resources", $DBFieldName."Precision"))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `".$DBFieldName."Precision`"
                               ." INT ".($Optional ? "" : "NOT NULL"));
                }
                break;

            case MetadataSchema::MDFTYPE_TIMESTAMP:
                # add fields to resources table (if not already present)
                if (!$DB->FieldExists("Resources", $DBFieldName))
                {
                    $DB->Query("ALTER TABLE Resources ADD COLUMN `".$DBFieldName
                               ."` DATETIME ".($Optional ? "" : "NOT NULL"));
                }
                break;

            case MetadataSchema::MDFTYPE_TREE:
            case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
            case MetadataSchema::MDFTYPE_OPTION:
            case MetadataSchema::MDFTYPE_REFERENCE:
                break;

            default:
                exit("<br>ERROR:  Attempt to add database fields for illegal metadata field type<br>\n");
                break;
        }
    }

    /**
    * Translate string with boolean or one or more PHP constant names into
    * corresponding values.
    * @param CString String containing boolean or constant(s).
    * @param ClassName Name of class in which to search for constant.  (OPTIONAL)
    * @return Constant value(s) found.  (Multiple values are ORed together.)
    */
    private function TranslateStringToConstants($CString, $ClassName = NULL)
    {
        # if not a string return value unchanged to caller
        if (!is_string($CString) || ($CString === DB_NOVALUE))
        {
            $ReturnValue = $CString;
        }
        # handle booleans as a special case
        elseif (strtoupper(trim($CString)) == "TRUE")
        {
            $ReturnValue = TRUE;
        }
        elseif (strtoupper(trim($CString)) == "FALSE")
        {
            $ReturnValue = FALSE;
        }
        else
        {
            # assume no values will be found
            $ReturnValue = NULL;

            # split apart any ORed-together values
            $Values = explode("|", $CString);

            # for each value found
            foreach ($Values as $Value)
            {
                # trim off any extraneous whitespace
                $Value = trim($Value);

                # add class name prefix to constant name if requested
                if ($ClassName) {  $Value = $ClassName."::".$Value;  }

                # if value corresponds to a constant
                if (defined($Value))
                {
                    # add constant to return value
                    $ReturnValue = ($ReturnValue === NULL)
                            ? constant($Value)
                            : ($ReturnValue | constant($Value));
                }
            }

            # if no corresponding constants were found
            if ($ReturnValue === NULL)
            {
                # return original value to caller
                $ReturnValue = $CString;
            }
        }

        # return result to caller
        return $ReturnValue;
    }

}
