<?PHP

#
#   FILE:  SPT--MetadataSchema.php
#
#   METHODS PROVIDED:
#       MetadataSchema()
#           - constructor
#        AddField($FieldName, $FieldType, $Optional = FALSE, $DefaultValue = NULL)
#           - add new metadata field
#        DropField($FieldId)
#           - delete metadata field
#        GetField($FieldId)
#           - retrieve field by ID
#        GetFieldByName($FieldName)
#           - retrieve field by name
#        GetFields($FieldTypes = NULL)
#           - retrieve array of fields
#       GetFieldTypes()
#           - retrieve array of field types (enumerated type => field name)
#       GetAllowedFieldTypes()
#           - retrieve array of field types that user can create (enumerated type => field name)
#       Status() {  return $this->ErrorStatus;  }
#           - return current error status of schema object
#       RemoveQualifierAssociations($QualifierIdOrObject)
#           - remove all metadata field associations for a given qualifier
#       QualifierIsInUse($QualifierIdOrObject)
#           - return whether qualifier is in use by metadata field
#       (SEE ALSO:  SPT--ItemFactory.php)
#
#   AUTHOR:  Edward Almasy
#
#   Part of the Scout Portal Toolkit
#   Copyright 2002-2003 Internet Scout Project
#   http://scout.wisc.edu
#

class MetadataSchema extends ItemFactory {

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

    # types of field ordering
    const MDFORDER_DISPLAY =  1;
    const MDFORDER_EDITING =  2;
    
    # metadata field types 
# (must parallel MetadataFields.FieldType declaration in SPT--CreateTables.sql 
#        and MetadataField::$FieldTypeDBEnums declaration below)
    const MDFTYPE_TEXT =            1;
    const MDFTYPE_PARAGRAPH =       2;
    const MDFTYPE_NUMBER =          4;
    const MDFTYPE_DATE =            8;
    const MDFTYPE_TIMESTAMP =       16;
    const MDFTYPE_FLAG =            32;
    const MDFTYPE_TREE =            64;
    const MDFTYPE_CONTROLLEDNAME =  128;
    const MDFTYPE_OPTION =          256;
    const MDFTYPE_USER =            512;
    const MDFTYPE_IMAGE =           1024;
    const MDFTYPE_FILE =            2048;
    const MDFTYPE_URL =             4096;
    const MDFTYPE_POINT =           8192;
    
# error status codes
    const MDFSTAT_OK =                 1;
    const MDFSTAT_DUPLICATENAME =      2;
    const MDFSTAT_DUPLICATEDBCOLUMN =  4;
    const MDFSTAT_ILLEGALNAME =        8;
    const MDFSTAT_FIELDDOESNOTEXIST =  16;

    # object constructor
    function MetadataSchema()
    {
        # set up item factory base class
        $this->ItemFactory("MetadataField", "MetadataFields", "FieldId");
        
        # start with field info caching enabled
        $this->CachingOn = TRUE;
    }

    # turn internal caching of field info on or off
    function CacheData($NewValue)
    {
        $this->CachingOn = $NewValue;
    }

    # add new metadata field
    function AddField($FieldName, $FieldType, $Optional = TRUE, $DefaultValue = NULL)
    {
        # create new field
        $Field = new MetadataField(NULL, $FieldName, $FieldType, $Optional, $DefaultValue);

        # save error code if create failed and return NULL
        if ($Field->Status() != MetadataSchema::MDFSTAT_OK)
        {
            $this->ErrorStatus = $Field->Status();
            $Field = NULL;
        }

        # return new field to caller
        return $Field;
    }

    # delete metadata field
    function DropField($FieldId)
    {
        $Field = new MetadataField($FieldId);
        $Field->Drop();
    }

    # retrieve field by ID
    function GetField($FieldId)
    {
        static $Fields;

        # if caching is off or field is already loaded
        if (($this->CachingOn != TRUE) || !isset($Fields[$FieldId]))
        {
            # retrieve field
            $Fields[$FieldId] = new MetadataField($FieldId);
        }

        # return field to caller
        return $Fields[$FieldId];
    }

    # retrieve field by name
    function GetFieldByName($FieldName, $IgnoreCase = FALSE)
    {
        $FieldId = $this->GetFieldIdByName($FieldName, $IgnoreCase);
        return ($FieldId === NULL) ? NULL : $this->GetField($FieldId);
    }

    # retrieve field ID by name
    function GetFieldIdByName($FieldName, $IgnoreCase = FALSE)
    {
        static $FieldIdsByName;

        # if caching is off or field ID is already loaded
        if (($this->CachingOn != TRUE) || !isset($FieldIdsByName[$FieldName]))
        {
            # retrieve field ID from DB
            $Condition = $IgnoreCase
                    ? "WHERE LOWER(FieldName) = '".addslashes(strtolower($FieldName))."'"
                    : "WHERE FieldName = '".addslashes($FieldName)."'";
            $FieldIdsByName[$FieldName] = $this->DB->Query(
                    "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId");
        }

        return $FieldIdsByName[$FieldName];
    }

    # check whether field with specified name exists
    function FieldExists($FieldName)
    {
        return $this->GetFieldByName($FieldName) ? TRUE : FALSE;
    }

    # retrieve array of fields
    function GetFields($FieldTypes = NULL, $OrderType = NULL, $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
    {
        # create empty array to pass back
        $Fields = array();

        # for each field type in database
        if ($IncludeTempFields && $IncludeDisabledFields)
        {
            $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields");
        }
        else
        {
            if ($IncludeTempFields)
            {
                $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE Enabled != 0");
            }
            elseif ($IncludeDisabledFields)
            {
                $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0");
            }
            else
            {
                $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0 AND Enabled != 0");
            }
        }
        while ($Record = $this->DB->FetchRow())
        {
            # if no specific type requested or if field is of requested type
            if (($FieldTypes == NULL)
                || (MetadataField::$FieldTypePHPEnums[$Record["FieldType"]] & $FieldTypes))
            {
                # create field object and add to array to be passed back
                $Fields[$Record["FieldId"]] = new MetadataField($Record["FieldId"]);
            }
        }

        # if field sorting requested
        if ($OrderType !== NULL)
        {
            # sort field array by requested order type
            $this->FieldCompareType = $OrderType;
            $MSFieldOrderError = FALSE;
            uasort($Fields, array($this, "CompareFieldOrder"));

            # if field order error detected
            global $MSFieldOrderError;
            if ($MSFieldOrderError)
            {
                # repair (reset) field order
                $OrderIndex = 1;
                foreach ($Fields as $Field)
                {
                    $Field->OrderPosition($OrderType, $OrderIndex);
                    $OrderIndex++;
                }
            }
        }

        # return array of field objects to caller
        return $Fields;
    }

    function GetFieldNames($FieldTypes = NULL, $OrderType = NULL,
                            $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
    {
        global $DB;

        $FieldNames=array();
        $Fields = $this->GetFields($FieldTypes, $OrderType, $IncludeDisabledFields, $IncludeTempFields);

        foreach($Fields as $Field)
        {
            $DB->Query("SELECT FieldName FROM MetadataFields WHERE FieldId=".$Field->Id());
            $FieldNames[ $Field->Id() ] = $DB->FetchField("FieldName");
        }

        return $FieldNames;
    }


    # retrieve array of field types (enumerated type => field name)
    function GetFieldTypes()
    {
        return MetadataField::$FieldTypeDBEnums;
    }

    # retrieve array of field types that user can create (enumerated type => field name)
    function GetAllowedFieldTypes()
    {
        return MetadataField::$FieldTypeDBAllowedEnums;
    }

    # remove all metadata field associations for a given qualifier
    function RemoveQualifierAssociations($QualifierIdOrObject)
    {
        # sanitize qualifier ID or grab it from object
        $QualifierIdOrObject = is_object($QualifierIdOrObject)
                ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);

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

    # return whether qualifier is in use by metadata field
    function QualifierIsInUse($QualifierIdOrObject)
    {
        # sanitize qualifier ID or grab it from object
        $QualifierIdOrObject = is_object($QualifierIdOrObject)
                ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);

        # determine whether any fields use qualifier as default
        $DefaultCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM MetadataFields"
                                         ." WHERE DefaultQualifier = ".$QualifierIdOrObject,
                                         "RecordCount");

        # determine whether any fields are associated with qualifier
        $AssociationCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM FieldQualifierInts"
                                         ." WHERE QualifierId = ".$QualifierIdOrObject,
                                         "RecordCount");

        # report whether qualifier is in use based on defaults and associations
        return (($DefaultCount + $AssociationCount) > 0) ? TRUE : FALSE;
    }

    # move fields up or down in field order
    function MoveUpInOrder($FieldIdOrObj, $OrderType)
    {
        $this->MoveFieldInOrder($FieldIdOrObj, $OrderType, FALSE);
    }
    function MoveDownInOrder($FieldIdOrObj, $OrderType)
    {
        $this->MoveFieldInOrder($FieldIdOrObj, $OrderType, TRUE);
    }

    # return highest field ID currently in use
    function GetHighestFieldId()
    {
        return $this->GetHighestItemId();
    }


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

    var $FieldCompareType;
    var $CachingOn;

    function MoveFieldInOrder($FieldIdOrObj, $OrderType, $MoveFieldDown)
    {
        # grab field ID
        $FieldId = is_object($FieldIdOrObj) ? $Field->Id() : $FieldIdOrObj;

        # retrieve array of fields
        $Fields = $this->GetFields(NULL, $OrderType);

        # reverse array of fields if we are moving field down
        if ($MoveFieldDown)
        {
            $Fields = array_reverse($Fields);
        }

        # for each field in order
        $PreviousField = NULL;
        foreach ($Fields as $Field)
        {
            # if field is the field to be moved
            if ($Field->Id() == $FieldId)
            {
                # if we have a previous field
                if ($PreviousField !== NULL)
                {
                    # swap field with previous field according to order type
                    $TempVal = $Field->OrderPosition($OrderType);
                    $Field->OrderPosition($OrderType, $PreviousField->OrderPosition($OrderType));
                    $PreviousField->OrderPosition($OrderType, $TempVal);
                }
            }

            # save field for next iteration
            $PreviousField = $Field;
        }
    }

    # callback function for sorting fields
    function CompareFieldOrder($FieldA, $FieldB)
    {
        if ($FieldA->OrderPosition($this->FieldCompareType) == $FieldB->OrderPosition($this->FieldCompareType))
        {
            global $MSFieldOrderError;
            $MSFieldOrderError = TRUE;
            return 0;
        }
        else
        {
            return ($FieldA->OrderPosition($this->FieldCompareType) < $FieldB->OrderPosition($this->FieldCompareType)) ? -1 : 1;
        }
    }
}

?>
