<?PHP
#
#   FILE:  SPTOAIServer.php
#
#   Copyright 2002-2010 Internet Scout
#   http://scout.wisc.edu
#

class SPTOAIServer extends OAIServer {

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

    function SPTOAIServer($RetrievalSearchParameters = NULL)
    {
        global $SysConfig;

        # grab our own database handle
        $this->DB = new SPTDatabase();
        $DB =& $this->DB;

        # set up repository description
        $DB->Query("SELECT * FROM SystemConfiguration");
        $Record = $DB->FetchRow();
        $RepDescr["Name"]            = $SysConfig->PortalName();
        $ServerName = ($_SERVER["SERVER_NAME"] != "127.0.0.1")
                ? $_SERVER["SERVER_NAME"]
                : $_SERVER["HTTP_HOST"];
        $RepDescr["BaseURL"]         = "http://".$ServerName.$_SERVER["SCRIPT_NAME"];
        $RepDescr["DateGranularity"] = "DATE";
        $RepDescr["EarliestDate"]    = strlen(trim($Record["OaiEarliestDate"]))
                ? $Record["OaiEarliestDate"] : "1990-01-01";
        $RepDescr["AdminEmail"][]    = $SysConfig->AdminEmail();
        $RepDescr["IDDomain"]        = strlen(trim($Record["OaiIdDomain"]))
                ? $Record["OaiIdDomain"] : $ServerName;
        $RepDescr["IDPrefix"]        = strlen(trim($Record["OaiIdPrefix"]))
                ? $Record["OaiIdPrefix"] : $ServerName;

        # create item factory object for retrieving items from DB
        $this->SPTItemFactory = new SPTOAIItemFactory($RetrievalSearchParameters);

        # call parent's constructor
        $this->OAIServer($DB, $RepDescr, $this->SPTItemFactory, TRUE, $SysConfig->OAISQEnabled());

        # set up description of nsdl_dc format
        $NsdldcNamespaceList = array(
            "nsdl_dc" => "http://ns.nsdl.org/nsdl_dc_v1.01",
            "dc" => "http://purl.org/dc/elements/1.1/",
            "dct" => "http://purl.org/dc/terms/",
            "ieee" => "http://www.ieee.org/xsd/LOMv1p0",
            );
        $NsdldcElements = array(
            "dc:title",
            "dc:creator",
            "dc:subject",
            "dc:description",
            "dc:publisher",
            "dc:contributor",
            "dc:date",
            "dc:type",
            "dc:format",
            "dc:identifier",
            "dc:source",
            "dc:language",
            "dc:relation",
            "dc:coverage",
            "dc:rights",
            "dct:audience",
            "dct:alternative",
            "dct:tableOfContents",
            "dct:abstract",
            "dct:created",
            "dct:valid",
            "dct:available",
            "dct:issued",
            "dct:modified",
            "dct:extent",
            "dct:medium",
            "dct:isVersionOf",
            "dct:hasVersion",
            "dct:isReplacedBy",
            "dct:replaces",
            "dct:isRequiredBy",
            "dct:requires",
            "dct:isPartOf",
            "dct:hasPart",
            "dct:isReferencedBy",
            "dct:references",
            "dct:isFormatOf",
            "dct:hasFormat",
            "dct:conformsTo",
            "dct:spatial",
            "dct:temporal",
            "dct:mediator",
            "dct:dateAccepted",
            "dct:dateCopyrighted",
            "dct:dateSubmitted",
            "dct:educationLevel",
            "dct:accessRights",
            "dct:bibliographicCitation",
            "ieee:interactivityType",
            "ieee:interactivityLevel",
            "ieee:typicalLearningTime",
            );
        $NsdldcQualifiers = array(
            "dct:LCSH",
            "dct:MESH",
            "dct:DDC",
            "dct:LCC",
            "dct:UDC",
            "dct:DCMIType",
            "dct:IMT",
            "dct:ISO639-2",
            "dct:RFC1766",
            "dct:URI",
            "dct:Point",
            "dct:ISO3166",
            "dct:Box",
            "dct:TGN",
            "dct:Period",
            "dct:W3CDTF",
            "dct:RFC3066",
            );
        $this->AddFormat("nsdl_dc", "nsdl_dc:nsdl_dc",
                         "http://ns.nsdl.org/nsdl_dc_v1.01"
                         ." http://ns.nsdl.org/schemas/nsdl_dc/nsdl_dc_v1.01.xsd",
                         "1.01.001",
                         $NsdldcNamespaceList, $NsdldcElements, $NsdldcQualifiers);

        # load field mappings from database and set in parent
        $Schema = new MetadataSchema();
        $DB->Query("SELECT * FROM OAIFieldMappings");
        while ($Record = $DB->FetchRow())
        {
            if ($Record["OAIFieldName"] != "Unmapped")
            {
                parent::SetFieldMapping($Record["FormatName"],
                                        $Record["SPTFieldId"],
                                        $Record["OAIFieldName"]);
            }
        }

        # load qualifier mappings from database and set in parent
        $DB->Query("SELECT * FROM OAIQualifierMappings");
        while ($Record = $DB->FetchRow())
        {
            if ($Record["OAIQualifierName"] != "Unmapped")
            {
                $LocalQualifier = new Qualifier($Record["SPTQualifierId"]);
                $LocalQualifierName = $LocalQualifier->Name();
                parent::SetQualifierMapping($Record["FormatName"],
                                        $LocalQualifierName,
                                        $Record["OAIQualifierName"]);
            }
        }
    }

    # add SQL conditional for selecting resources
    function AddSQLConditionalForResources($Conditional)
    {
        # pass conditional on to item factory
        $this->SPTItemFactory->AddSQLConditionalForResources($Conditional);
    }

    # get/set mapping of local field to OAI field (overloads parent method)
    function GetFieldMapping($FormatName, $LocalFieldName)
    {
        # retrieve ID for local field
        $Schema = new MetadataSchema();
        $LocalField = $Schema->GetFieldByName($LocalFieldName);
        $LocalFieldId = $LocalField->Id();

        # return stored value
        return parent::GetFieldMapping($FormatName, $LocalFieldId);
    }
    function SetFieldMapping($FormatName, $LocalFieldName, $OAIFieldName)
    {
        # retrieve ID for local field
        $Schema = new MetadataSchema();
        $LocalField = $Schema->GetFieldByName($LocalFieldName);
        $LocalFieldId = $LocalField->Id();

        # check whether mapping is already in database
        $DB =& $this->DB;
        $MapCount = $DB->Query("SELECT COUNT(*) AS MapCount FROM OAIFieldMappings"
                               ." WHERE FormatName = '".$FormatName."'"
                               ." AND SPTFieldId = '".$LocalFieldId."'",
                               "MapCount");

        # if mapping is already in database
        if ($MapCount > 0)
        {
            # change mapping in database
            $DB->Query("UPDATE OAIFieldMappings"
                       ." SET OAIFieldName = '".addslashes($OAIFieldName)."'"
                       ." WHERE FormatName = '".addslashes($FormatName)."'"
                       ." AND SPTFieldId = '".$LocalFieldId."'");
        }
        else
        {
            # add new mapping to database
            $DB->Query("INSERT INTO OAIFieldMappings"
                       ." (FormatName, SPTFieldId, OAIFieldName) VALUES"
                       ." ('".addslashes($FormatName)."', '".$LocalFieldId
                       ."', '".addslashes($OAIFieldName)."')");
        }

        # call parent method
        parent::SetFieldMapping($FormatName, $LocalFieldId, $OAIFieldName);
    }

    # set mapping of local qualifier to OAI qualifier (overloads parent method)
    function SetQualifierMapping($FormatName, $LocalQualifierName, $OAIQualifierName)
    {
        # retrieve ID for local qualifier
        $QFactory = new QualifierFactory();
        $LocalQualifier = $QFactory->GetQualifierByName($LocalQualifierName);
        $LocalQualifierId = $LocalQualifier->Id();

        # check whether mapping is already in database
        $DB =& $this->DB;
        $MapCount = $DB->Query("SELECT COUNT(*) AS MapCount FROM OAIQualifierMappings"
                               ." WHERE FormatName = '".addslashes($FormatName)."'"
                               ." AND SPTQualifierId = '".$LocalQualifierId."'",
                               "MapCount");

        # if mapping is already in database
        if ($MapCount > 0)
        {
            # change mapping in database
            $DB->Query("UPDATE OAIQualifierMappings"
                       ." SET OAIQualifierName = '".addslashes($OAIQualifierName)."'"
                       ." WHERE FormatName = '".addslashes($FormatName)."'"
                       ." AND SPTQualifierId = '".$LocalQualifierId."'");
        }
        else
        {
            # add new mapping to database
            $DB->Query("INSERT INTO OAIQualifierMappings"
                       ." (FormatName, SPTQualifierId, OAIQualifierName) VALUES"
                       ." ('".addslashes($FormatName)."', '".$LocalQualifierId
                       ."', '".addslashes($OAIQualifierName)."')");
        }

        # call parent method
        parent::SetQualifierMapping($FormatName, $LocalQualifierName, $OAIQualifierName);
    }


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

    var $DB;
    var $SPTItemFactory;

}

class SPTOAIItemFactory extends OAIItemFactory {

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

    # object constructor
    function SPTOAIItemFactory($RetrievalSearchParameters = NULL)
    {
        # save any supplied retrieval parameters
        $this->RetrievalSearchParameters = $RetrievalSearchParameters;
    }

    function GetItem($ItemId)
    {
        # add link to full record page for item
        $ServerName = ($_SERVER["SERVER_NAME"] != "127.0.0.1")
                ? $_SERVER["SERVER_NAME"]
                : $_SERVER["HTTP_HOST"];
        $SearchInfo["fullRecordLink"] = "http://".$ServerName.dirname($_SERVER["SCRIPT_NAME"])."/SPT--FullRecord.php?ResourceId=".$ItemId;

        # if a search score is available for the item
        if (isset($this->SearchScores) && isset($this->SearchScores[$ItemId]))
        {
            # add search info for item
            $SearchInfo["searchScore"] = $this->SearchScores[$ItemId];
            $SearchInfo["searchScoreScale"] = $this->SearchScoreScale;
        }

        # attempt to create item
        $Item = new SPTOAIItem($ItemId, $SearchInfo);

        # if item creation failed
        if ($Item->Status() == -1)
        {
            # return NULL to indicate that no item was found with that ID
            return NULL;
        }
        else
        {
            # return item to caller
            return $Item;
        }
    }

    function GetItems($StartingDate = NULL, $EndingDate = NULL)
    {
        return $this->GetItemsInSet(NULL, $StartingDate, $EndingDate);
    }

    function GetItemsInSet($Set, $StartingDate = NULL, $EndingDate = NULL)
    {
        # initialize search parameters with release flag requirement
        $SearchStrings["Release Flag"] = "=1";

        # if both begin and end date supplied
        if (($StartingDate != NULL) && ($EndingDate != NULL))
        {
            # select resources created between starting and ending dates
            $SearchStrings["Date Of Record Creation"] =
                    array(">=".$StartingDate, "<=".$EndingDate);
        }
        # else if begin date specified
        elseif ($StartingDate != NULL)
        {
            # select resources created after begin date
            $SearchStrings["Date Of Record Creation"] = ">=".$StartingDate;
        }
        # else if end date specified
        elseif ($EndingDate != NULL)
        {
            # select resources created after begin date
            $SearchStrings["Date Of Record Creation"] = "<=".$EndingDate;
        }

        # if set specified
        if ($Set != NULL)
        {
            # load set mappings
            $this->LoadSetNameInfo();

            # if set is valid
            if (isset($this->SetFields[$Set]))
            {
                # add field spec to search strings
                $SearchStrings[$this->SetFields[$Set]] = "= ".$this->SetValues[$Set];
            }
            else
            {
                # set will not match anything so return empty array to caller
                return array();
            }
        }

        # perform search for desired items
        $Engine = new SPTSearchEngine();
        if ($this->RetrievalSearchParameters)
        {
            $SearchStrings = array_merge($SearchStrings,
                    $this->RetrievalSearchParameters);
        }
        $SearchResults = $Engine->FieldedSearch($SearchStrings, 0, 1000000);

        # extract resource IDs from search results
        $ItemIds = array_keys($SearchResults);

        # return array of resource IDs to caller
        return $ItemIds;
    }

    # return array containing all set specs (with human-readable set names as keys)
    function GetListOfSets()
    {
        # make sure set name info is loaded
        $this->LoadSetNameInfo();

        # return list of sets to caller
        return $this->SetSpecs;
    }

    # retrieve IDs of items that match search parameters (only needed if OAI-SQ supported)
    function SearchForItems($SearchParams, $StartingDate = NULL, $EndingDate = NULL)
    {
        # translate field IDs into field names for search parameters
        $Schema = new MetadataSchema;
        foreach ($SearchParams as $FieldId => $Value)
        {
            if ($FieldId == "X-KEYWORD-X")
            {
                $SearchStrings["XXXKeywordXXX"] = $Value;
            }
            else
            {
                $Field = $Schema->GetField($FieldId);
                $SearchStrings[$Field->Name()] = $Value;
            }
        }

        # add release flag requirement to search parameters
        $SearchStrings["Release Flag"] = "=1";

        # if both begin and end date supplied
        if (($StartingDate != NULL) && ($EndingDate != NULL))
        {
            # select resources created between starting and ending dates
            $SearchStrings["Date Of Record Creation"] =
                    array(">=".$StartingDate, "<=".$EndingDate);
        }
        # else if begin date specified
        elseif ($StartingDate != NULL)
        {
            # select resources created after begin date
            $SearchStrings["Date Of Record Creation"] = ">=".$StartingDate;
        }
        # else if end date specified
        elseif ($EndingDate != NULL)
        {
            # select resources created after begin date
            $SearchStrings["Date Of Record Creation"] = "<=".$EndingDate;
        }

        # perform search for desired items
        $Engine = new SPTSearchEngine();
        if ($this->RetrievalSearchParameters)
        {
            $SearchStrings = array_merge($SearchStrings,
                    $this->RetrievalSearchParameters);
        }
        $SearchResults = $Engine->FieldedSearch($SearchStrings, 0, 1000000);

        # save search scores
        $this->SearchScores = $SearchResults;
        $this->SearchScoreScale = $Engine->FieldedSearchWeightScale($SearchStrings);

        # extract resource IDs from search results
        $ItemIds = array_keys($SearchResults);

        # return array of resource IDs to caller
        return $ItemIds;
    }


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

    var $SetSpecs;
    var $SetFields;
    var $SetValues;
    var $RetrievalSearchParameters;
    var $SearchScores;
    var $SearchScoreScale;

    # normalize value for use as an OAI set spec
    function NormalizeForSetSpec($Name)
    {
        return preg_replace("/[^a-zA-Z0-9\-_.!~*'()]/", "", $Name);
    }

    # load normalized set names and name mappings
    function LoadSetNameInfo()
    {
        # if set names have not already been loaded
        if (!isset($this->SetSpecs))
        {
            # start with empty list of sets
            $this->SetSpecs = array();
            $this->SetFields = array();
            $this->SetValues = array();

            # for each metadata field that is a type that can be used for sets
            $Schema = new MetadataSchema();
            $Fields = $Schema->GetFields(MetadataSchema::MDFTYPE_TREE|MetadataSchema::MDFTYPE_CONTROLLEDNAME|MetadataSchema::MDFTYPE_OPTION);
            foreach ($Fields as $Field)
            {
                # if field is flagged as being used for OAI sets
                if ($Field->UseForOaiSets())
                {
                    # retrieve all possible values for field
                    $FieldValues = $Field->GetPossibleValues();

                    # prepend field name to each value and add to list of sets
                    $FieldName = $Field->Name();
                    $NormalizedFieldName = $this->NormalizeForSetSpec($FieldName);
                    foreach ($FieldValues as $Value)
                    {
                        $SetSpec = $NormalizedFieldName.":"
                                .$this->NormalizeForSetSpec($Value);
                        $this->SetSpecs[$FieldName.": ".$Value] = $SetSpec;
                        $this->SetFields[$SetSpec] = $FieldName;
                        $this->SetValues[$SetSpec] = $Value;
                    }
                }
            }
        }
    }
}

class SPTOAIItem extends OAIItem {

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

    # object constructor
    function SPTOAIItem($ItemId, $SearchInfo = NULL)
    {
        # save ID for later use
        $this->Id = $ItemId;

        # save any search info supplied
        $this->SearchInfo = $SearchInfo;

        # attempt to create resource object
        $this->Resource = new Resource($ItemId);

        # if resource object creation failed
        if ($this->Resource->Status() == -1)
        {
            # set status to -1 to indicate constructor failure
            $this->LastStatus = -1;
        }
        else
        {
            # set status to 1 to indicate constructor success
            $this->LastStatus = 1;

            # if cumulative rating data is available for this resource
            global $SysConfig;
            if ($SysConfig->ResourceRatingsEnabled()
                    && $this->Resource->CumulativeRating())
            {
                # add cumulative rating data to search info
                $this->SearchInfo["cumulativeRating"] =
                        $this->Resource->CumulativeRating();
                $this->SearchInfo["cumulativeRatingScale"] = 100;
            }
        }
    }

    function GetId() {  return $this->Id;  }

    function GetDatestamp()
    {
        $DateString = $this->Resource->Get("Date Of Record Creation");
        if ($DateString == "0000-00-00 00:00:00") {  $DateString = date("Y-m-d");  }
        $Date = new Date($DateString);
        return $Date->FormattedISO8601();
    }

    function GetValue($ElementName)
    {
        # retrieve value
        $ReturnValue = $this->Resource->GetByFieldId($ElementName);

        # strip out any HTML tags if text value
        if (is_string($ReturnValue))
        {
            $ReturnValue = strip_tags($ReturnValue);
        }

        # format correctly if standardized date
        if ($this->GetQualifier($ElementName) == "W3C-DTF")
        {
            $Timestamp = strtotime($ReturnValue);
            $ReturnValue = date('Y-m-d\TH:i:s', $Timestamp)
                    .substr_replace(date('O', $Timestamp), ':', 3, 0);
        }

        # return value to caller
        return $ReturnValue;
    }

    function GetQualifier($ElementName)
    {
        $ReturnValue = NULL;
        $Qualifier = $this->Resource->GetQualifierByFieldId($ElementName, TRUE);
        if (is_array($Qualifier))
        {
            foreach ($Qualifier as $ItemId => $QualObj)
            {
                if (is_object($QualObj))
                {
                    $ReturnValue[$ItemId] = $QualObj->Name();
                }
            }
        }
        else
        {
            if (isset($Qualifier) && is_object($Qualifier))
            {
                $ReturnValue = $Qualifier->Name();
            }
        }
        return $ReturnValue;
    }

    function GetSets()
    {
        # start out with empty list
        $Sets = array();

        # for each possible metadata field
        $Schema = new MetadataSchema();
        $Fields = $Schema->GetFields(MetadataSchema::MDFTYPE_TREE|MetadataSchema::MDFTYPE_CONTROLLEDNAME|MetadataSchema::MDFTYPE_OPTION);
        foreach ($Fields as $Field)
        {
            # if field is flagged for use for OAI sets
            if ($Field->UseForOaiSets())
            {
                # retrieve values for resource for this field and add to set list
                $FieldName = $Field->Name();
                $Values = $this->Resource->Get($FieldName);
                if (isset($Values) && ($Values != NULL))
                {
                    $NormalizedFieldName = $this->NormalizeForSetSpec($FieldName);
                    if (is_array($Values) && count($Values))
                    {
                        foreach ($Values as $Value)
                        {
                            $Sets[] = $NormalizedFieldName.":"
                                    .$this->NormalizeForSetSpec($Value);
                        }
                    }
                    else
                    {
                        $Sets[] = $NormalizedFieldName.":"
                                .$this->NormalizeForSetSpec($Values);
                    }
                }
            }
        }

        # return list of sets to caller
        return $Sets;
    }

    function GetSearchInfo()
    {
        return $this->SearchInfo;
    }

    function Status()
    {
        return $this->LastStatus;
    }


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

    var $Id;
    var $Resource;
    var $LastStatus;
    var $SearchInfo;

    # normalize value for use as an OAI set spec
    function NormalizeForSetSpec($Name)
    {
        return preg_replace("/[^a-zA-Z0-9\-_.!~*'()]/", "", $Name);
    }
}
