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

/**
* Factory for Resource objects.
*/
class ResourceFactory extends ItemFactory
{
    # ---- PUBLIC INTERFACE --------------------------------------------------

    /**
    * Class constructor.
    * @param int $SchemaId ID of schema to load resources for.  (OPTIONAL,
    *       defaults to SCHEMAID_DEFAULT)
    */
    public function __construct($SchemaId = MetadataSchema::SCHEMAID_DEFAULT)
    {
        # save schema ID
        $this->SchemaId = $SchemaId;

        # set up resource count cache
        $this->CountCache = array();

        # set up item factory base class
        parent::__construct("Resource", "Resources", "ResourceId", NULL, FALSE,
                "SchemaId = ".intval($this->SchemaId));
    }

    /**
    * Duplicate the specified resource and return to caller.
    * @param int $ResourceId ID of resource to duplicate.
    * @return object New Resource object.
    */
    public function DuplicateResource($ResourceId)
    {
        # check that resource to be duplicated exists
        if (!Resource::ItemExists($ResourceId))
        {
            throw new InvalidArgumentException(
                    "No resource found with specified ID (".$ResourceId.").");
        }

        # create new target resource
        $DstResource = Resource::Create($this->SchemaId);

        # load up resource to duplicate
        $SrcResource = new Resource($ResourceId);

        # for each metadata field
        $Schema = new MetadataSchema($this->SchemaId);
        $Fields = $Schema->GetFields();
        foreach ($Fields as $Field)
        {
            if ($Field->CopyOnResourceDuplication())
            {
                $NewValue = $SrcResource->GetByField($Field, TRUE);

                # clear default value from destination resource that is
                # set when creating a new resource
                $DstResource->ClearByField($Field);

                # copy value from source resource to destination resource
                $DstResource->SetByField($Field, $NewValue);
            }
        }

        # return new resource to caller
        return $DstResource;
    }

    /**
    * Clear or change specific qualifier for all resources.
    * @param mixed $ObjectOrId Qualifier ID or object to clear or change.
    * @param mixed $NewObjectOrId New Qualifier ID or object.  (OPTIONAL, defaults
    *       to NULL, which will clear old qualifier)
    */
    public function ClearQualifier($ObjectOrId, $NewObjectOrId = NULL)
    {
        # sanitize qualifier ID or retrieve from object
        $QualifierId = is_object($ObjectOrId)
                ?  $ObjectOrId->Id() : intval($ObjectOrId);

        # if new qualifier passed in
        if ($NewObjectOrId !== NULL)
        {
            # sanitize qualifier ID to change to or retrieve it from object
            $NewQualifierIdVal = is_object($NewObjectOrId)
                    ?  $NewObjectOrId->Id() : intval($NewObjectOrId);
        }
        else
        {
            # qualifier should be cleared
            $NewQualifierIdVal = "NULL";
        }

        # for each metadata field
        $Schema = new MetadataSchema($this->SchemaId);
        $Fields = $Schema->GetFields();
        foreach ($Fields as $Field)
        {
            # if field uses qualifiers and uses item-level qualifiers
            $QualColName = $Field->DBFieldName()."Qualifier";
            if ($Field->UsesQualifiers()
                && $Field->HasItemLevelQualifiers()
                && $this->DB->FieldExists("Resources", $QualColName))
            {
                # set all occurrences to new qualifier value
                $this->DB->Query("UPDATE Resources"
                           ." SET ".$QualColName." = ".$NewQualifierIdVal.""
                           ." WHERE ".$QualColName." = '".$QualifierId."'"
                           ." AND SchemaId = ".intval($this->SchemaId));
            }
        }

        # clear or change qualifier association with controlled names
        # (NOTE: this should probably be done in a controlled name factory object)
        $this->DB->Query("UPDATE ControlledNames"
                   ." SET QualifierId = ".$NewQualifierIdVal
                   ." WHERE QualifierId = '".$QualifierId."'");

        # clear or change qualifier association with classifications
        # (NOTE: this should probably be done in a classification factory object)
        $this->DB->Query("UPDATE Classifications"
                   ." SET QualifierId = ".$NewQualifierIdVal
                   ." WHERE QualifierId = '".$QualifierId."'");
    }

    /**
    * Return number of resources that have ratings.
    * @return int Resource count.
    */
    public function GetRatedResourceCount()
    {
        return $this->DB->Query(
                "SELECT COUNT(DISTINCT ResourceId) AS ResourceCount"
                        ." FROM ResourceRatings",
                "ResourceCount");
    }

    /**
    * Return number of users who have rated resources.
    * @return int User count.
    */
    public function GetRatedResourceUserCount()
    {
        return $this->DB->Query(
                "SELECT COUNT(DISTINCT UserId) AS UserCount"
                        ." FROM ResourceRatings",
                "UserCount");
    }

    /**
    * Get resources sorted by descending Date of Record Release, with Date of
    * Record Creation as the secondary sort criteria..
    * @param int $Count Maximum number of resources to return.
    * @param int $Offset Starting offset of segment to return (0=beginning).
    * @param int $MaxDaysToGoBack Maximum number of days to go back for
    *       resources, according to Date of Record Release.
    * @return array Array of Resource objects.
    */
    public function GetRecentlyReleasedResources(
        $Count = 10, $Offset = 0, $MaxDaysToGoBack = 90)
    {
        # assume that no resources will be found
        $Resources = array();

        # calculate cutoff date for resources
        $CutoffDate = date("Y-m-d H:i:s", strtotime($MaxDaysToGoBack." days ago"));

        # query for resource IDs
        $this->DB->Query("SELECT ResourceId FROM Resources WHERE"
                ." DateOfRecordRelease > '".$CutoffDate."'"
                ." AND ReleaseFlag = 1"
                ." AND ResourceId >= 0"
                ." AND SchemaId = ".intval($this->SchemaId)
                ." ORDER BY DateOfRecordRelease DESC, DateOfRecordCreation DESC"
                ." LIMIT ".intval($Offset).", ".intval($Count));
        $ResourceIds = $this->DB->FetchColumn("ResourceId");

        # for each resource ID found
        foreach ($ResourceIds as $ResourceId)
        {
            # load resource and add to list of found resources
            $Resources[$ResourceId] = new Resource($ResourceId);
        }

        # return found resources to caller
        return $Resources;
    }

    /**
    * Get resource IDs sorted by specified field.  Only IDs for resources
    * with non-empty non-null values for the specified field are returned.
    * @param mixed $FieldId ID or name of field.
    * @param bool $Ascending If TRUE, sort is ascending, otherwise sort is descending.
    * @param int $Limit Number of IDs to retrieve.  (OPTIONAL)
    * @return array Resource IDs.
    */
    public function GetResourceIdsSortedBy($FieldId, $Ascending = TRUE, $Limit = NULL)
    {
        # assume no resources will be found
        $ResourceIds = array();

        # get field
        $Schema = new MetadataSchema($this->SchemaId);
        $Field = $Schema->GetField($FieldId);

        # if field was found
        if ($Field != NULL)
        {
            # construct query based on field type
            switch ($Field->Type())
            {
                case MetadataSchema::MDFTYPE_TEXT:
                case MetadataSchema::MDFTYPE_PARAGRAPH:
                case MetadataSchema::MDFTYPE_URL:
                    $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
                            ." FROM Resources WHERE "
                            .$Field->DBFieldName()." IS NOT NULL"
                            ." AND LENGTH(LTRIM(RTRIM(".$Field->DBFieldName()."))) > 0"
                            ." AND SchemaId = ".intval($this->SchemaId),
                            "ResourceCount");
                    if ($Count > 0)
                    {
                        $Query = "SELECT ResourceId FROM Resources"
                                ." WHERE SchemaId = ".intval($this->SchemaId)
                                ." ORDER BY ".$Field->DBFieldName()
                                .($Ascending ? " ASC" : " DESC");
                    }
                    break;

                case MetadataSchema::MDFTYPE_NUMBER:
                case MetadataSchema::MDFTYPE_TIMESTAMP:
                    $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
                            ." FROM Resources WHERE "
                            .$Field->DBFieldName()." IS NOT NULL"
                            ." AND SchemaId = ".intval($this->SchemaId),
                            "ResourceCount");
                    if ($Count > 0)
                    {
                        $Query = "SELECT ResourceId FROM Resources"
                                ." WHERE SchemaId = ".intval($this->SchemaId)
                                ." ORDER BY ".$Field->DBFieldName()
                                .($Ascending ? " ASC" : " DESC");
                    }
                    break;

                case MetadataSchema::MDFTYPE_DATE:
                    $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
                            ." FROM Resources WHERE "
                            .$Field->DBFieldName()."Begin IS NOT NULL"
                            ." AND SchemaId = ".intval($this->SchemaId),
                            "ResourceCount");
                    if ($Count > 0)
                    {
                        $Query = "SELECT ResourceId FROM Resources"
                                ." WHERE SchemaId = ".intval($this->SchemaId)
                                ." ORDER BY ".$Field->DBFieldName()."Begin"
                                .($Ascending ? " ASC" : " DESC");
                    }
                    break;
            }

            # if appropriate query was found
            if (isset($Query))
            {
                # if limited number of results were requested
                if ($Limit !== NULL)
                {
                    # add limit to query
                    $Query .= " LIMIT ".intval($Limit);
                }

                # perform query and retrieve resource IDs
                $this->DB->Query($Query);
                $ResourceIds = $this->DB->FetchColumn("ResourceId");
            }
        }

        # return resource IDs to caller
        return $ResourceIds;
    }

    /**
    * Filter a list of resources leaving only those viewable by a specified user.
    * @param array $ResourceIds ResourceIds to check
    * @param CWUser $User User to use for check
    * @return array of ResourceIds (subset of $ResourceIds) that $User can view
    */
    public function FilterNonViewableResources($ResourceIds, $User)
    {
        $DB = new Database();
        $Schema = new MetadataSchema($this->SchemaId);

        # compute this user's class
        $UserClass = $this->ComputeUserClass($User);

        # generate an array where the keys are ResourceIds affected by
        # user comparisons for the current user
        $UserComparisonsRIDs = array_flip(
            $this->ResourcesWhereUserComparisonsMatterForViewing($User));

        # grab all the ResourceIds for this user class
        $DB->Query("SELECT ResourceId, CanView FROM UserPermsCache WHERE"
                   ." UserClass='".$UserClass."'");

        # filter out those not requested
        $Cache = array_intersect_key(
            $DB->FetchColumn("CanView", "ResourceId"),
            array_flip($ResourceIds) );

        # figure out which resources we didn't have cached values for
        # and iterate over those
        $MissingIds = array_diff( $ResourceIds, array_keys($Cache) );
        foreach ($MissingIds as $Id)
        {
            # evaluate perms for this resource
            $Resource = new Resource($Id);
            $CanView = $Resource->UserCanView($User, FALSE);

            # if this is a result we can cache, do so
            if ( !isset($UserComparisonRIDs[$Id]) )
            {
                $this->DB->Query(
                        "INSERT INTO UserPermsCache (ResourceId, UserClass, CanView) "
                        ."VALUES (".$Id.",'".$UserClass."',".($CanView?"1":"0").")");
            }

            $Cache[$Id] = $CanView;
        }

        # apply schema permissions hooks to all our values
        foreach (array_keys($Cache) as $Id)
        {
            $SignalResult = $GLOBALS["AF"]->SignalEvent(
                "EVENT_RESOURCE_VIEW_PERMISSION_CHECK",
                array(
                    "Resource" => $Id,
                    "User" => $User,
                    "CanView" => $Cache[$Id],
                    "Schema" => $Schema, ));
            $Cache[$Id] = $SignalResult["CanView"];
        }

        # filter out the non-viewable resources, preserving the order
        # of resources
        $Result = array();
        foreach ($ResourceIds as $ResourceId)
        {
            if ($Cache[$ResourceId])
            {
                $Result[]= $ResourceId;
            }
        }

        # return the viewable ResourceIds
        return $Result;
    }

    /**
    * Clear the cache of viewable resources.
    */
    public function ClearViewingPermsCache()
    {
        $DB = new Database();
        $DB->Query("DELETE FROM UserPermsCache");
    }

    /**
    * Get date/time of when last a resource was modified.
    * @param bool $OnlyReleasedResources If TRUE, only resources with the
    *       Release Flag set are considered.
    * @return int Unix timestamp or NULL if no last modification time found.
    */
    public function GetTimestampOfLastResourceModification($OnlyReleasedResources = TRUE)
    {
        $LastChangeDate = $this->DB->Query(
                "SELECT MAX(DateLastModified) AS LastChangeDate"
                        ." FROM Resources"
                        ." WHERE SchemaId = ".intval($this->SchemaId)
                        .($OnlyReleasedResources ? " AND ReleaseFlag = 1" : ""),
                "LastChangeDate");
        return ($LastChangeDate ? strtotime($LastChangeDate) : NULL);
    }

    /**
    * Get possible field names for resources.
    * @return array Array of metadata field names with metadata field IDs
    *       for the index.
    */
    public function GetPossibleFieldNames()
    {
        # retrieve field names from schema
        $FieldNames = array();
        $Schema = new MetadataSchema($this->SchemaId);
        $Fields = $Schema->GetFields();
        foreach ($Fields as $Field)
        {
            $FieldNames[$Field->Id()] = $Field->Name();
        }

        # return field names to caller
        return $FieldNames;
    }

    /**
    * Find resources with values that match those specified.  (Only
    * works for Text, Paragraph, Number, Timestamp, Date, Flag, Url,
    * Point, and User fields.)
    * @param array $ValuesToMatch Array with metadata field IDs (or other values
    *       that can be resolved by MetadataSchema::GetCanonicalFieldIdentifier())
    *       for the index and string values to search for for the values.
    * @param bool $AllRequired TRUE to AND conditions together, FALSE to OR them
    *        (OPTIONAL, default TRUE)
    * @param bool $ReturnObjects TRUE to return Resource objects, FALSE for IDs
    * @return Array of Resource objects with resource IDs for index.
    */
    public function GetMatchingResources(
        $ValuesToMatch, $AllRequired=TRUE, $ReturnObjects=TRUE)
    {
        # start out assuming we won't find any resources
        $Resources = array();

        $LinkingTerm = "";
        $Condition = "";

        # for each value
        $Schema = new MetadataSchema($this->SchemaId);
        $Fields = $Schema->GetFields();

        foreach ($ValuesToMatch as $FieldId => $Value)
        {
            # retrieve metadata field ID if not supplied
            if (!is_numeric($FieldId))
            {
                $FieldId = $Schema->GetFieldIdByName($FieldId);
            }

            # if we're attempting to search a field that isn't in our schema,
            # throw an exception
            if (!isset($Fields[$FieldId]))
            {
                throw new Exception(
                    "Attempt to match values against a field "
                    ."that doesn't exist in this schema");
            }

            switch ($Fields[$FieldId]->Type())
            {
                case MetadataSchema::MDFTYPE_TEXT :
                case MetadataSchema::MDFTYPE_PARAGRAPH :
                case MetadataSchema::MDFTYPE_NUMBER :
                case MetadataSchema::MDFTYPE_DATE :
                case MetadataSchema::MDFTYPE_TIMESTAMP :
                case MetadataSchema::MDFTYPE_FLAG :
                case MetadataSchema::MDFTYPE_URL :
                    $DBFname = $Fields[$FieldId]->DBFieldName();
                    # add comparison to condition
                    if ($Value == "NULL")
                    {
                        $Condition .= $LinkingTerm."("
                                   .$DBFname." IS NULL OR ".$DBFname." = '')";
                    }
                    else
                    {
                        $Condition .= $LinkingTerm.$DBFname.
                                   " = '".addslashes($Value)."'";
                    }
                    break;

                case MetadataSchema::MDFTYPE_POINT:
                    $DBFname = $Fields[$FieldId]->DBFieldName();

                    if ($Value == "NULL")
                    {
                        $Condition .= $LinkingTerm."("
                                   .$DBFname."X IS NULL AND "
                                   .$DBFname."Y IS NULL)";
                    }
                    else
                    {
                        $Vx = addslashes($Value["X"]);
                        $Vy = addslashes($value["Y"]);

                        $Condition .= $LinkingTerm."("
                                   .$DBFname."X = '".$Vx."' AND "
                                   .$DBFname."Y = '".$Vy."')";
                    }
                    break;

                case MetadataSchema::MDFTYPE_USER:
                    $TgtValues = array();
                    if (is_object($Value))
                    {
                        $TgtValues[]= $Value->Id();
                    }
                    elseif (is_numeric($Value))
                    {
                        $TgtValues[]= $Value;
                    }
                    elseif (is_array($Value))
                    {
                        foreach ($Value as $UserId => $UserNameOrObject)
                        {
                            $TgtValues[]= $UserId;
                        }
                    }

                    # if no users were specified
                    if (!count($TgtValues))
                    {
                        # return no results (nothing matches nothing)
                        return array();
                    }
                    else
                    {
                        # add conditional to match specified users
                        $Condition .= $LinkingTerm."("
                                ."ResourceId IN (SELECT ResourceId FROM "
                                ."ResourceUserInts WHERE FieldId=".intval($FieldId)
                                ." AND UserId IN ("
                                .implode(",", $TgtValues).")) )";
                    }
                    break;

                default:
                    throw new Exception("Unsupported field type");
            }

            $LinkingTerm = $AllRequired ? " AND " : " OR ";
        }

        # if there were valid conditions
        if (strlen($Condition))
        {
            # build query statment
            $Query = "SELECT ResourceId FROM Resources WHERE (".$Condition
                   .") AND SchemaId = ".intval($this->SchemaId);

            # execute query to retrieve matching resource IDs
            $this->DB->Query($Query);
            $ResourceIds = $this->DB->FetchColumn("ResourceId");

            if ($ReturnObjects)
            {
                # retrieve resource objects
                foreach ($ResourceIds as $Id)
                {
                    $Resources[$Id] = new Resource($Id);
                }
            }
            else
            {
                $Resources = $ResourceIds;
            }
        }

        # return any resources found to caller
        return $Resources;
    }

    /**
    * Return the number of resources visible to a specified user that have a
    * given ControlledName value set.
    * @param string $ValueId Field valueid to look for
    * @param User $User User to check
    * @return int the number of associated resources or -1 when no count
    *      is available
    */
    public function AssociatedVisibleResourceCount($ValueId, $User)
    {
        # if the specified user is matched by any UserIs or UserIsNot
        # privset conditions for any resources, then put them in a class
        # by themselves
        $UserClass = count($this->ResourcesWhereUserComparisonsMatterForViewing($User))
                   ? "UID_".$User->Id() :
                   $this->ComputeUserClass($User);

        # if we haven't loaded any cached values, do so now
        if (!isset($this->CountCache[$UserClass]))
        {
            $this->DB->Query(
                "SELECT ResourceCount, ValueId FROM "
                ."VisibleResourceCounts WHERE "
                ."SchemaId=".intval($this->SchemaId)
                ." AND UserClass='".addslashes($UserClass)."'");

            $this->CountCache[$UserClass] = $this->DB->FetchColumn(
                "ResourceCount", "ValueId");
        }

        # if we don't have a cached value for this class,
        #   queue a background update and return -1
        if (!isset($this->CountCache[$UserClass][$ValueId]))
        {
            $GLOBALS["AF"]->QueueUniqueTask(
                array($this, "UpdateAssociatedVisibleResourceCount"),
                array($ValueId, $User->Id() ) );

            return -1;
        }

        # owtherwise, return the cached data
        return $this->CountCache[$UserClass][$ValueId];
    }

    /**
    * Update the count of resources associated with a
    * ControlledName that are visible to a specified user.
    * @param int $ValueId ControlledNameId to update.
    * @param int $UserId UserId to update.
    */
    public function UpdateAssociatedVisibleResourceCount(
        $ValueId, $UserId)
    {
        $User = new CWUser($UserId);

        # if the specified user is matched by any UserIs or UserIsNot
        # privset conditions for any resources, then put them in a class
        # by themselves
        $UserClass = count($this->ResourcesWhereUserComparisonsMatterForViewing($User))
                   ? "UID_".$User->Id() :
                   $this->ComputeUserClass($User);

        $this->DB->Query(
            "SELECT ResourceId FROM ResourceNameInts "
            ."WHERE ControlledNameId=".intval($ValueId) );
        $ResourceIds = $this->DB->FetchColumn("ResourceId");

        $ResourceIds = $this->FilterNonViewableResources(
            $ResourceIds, $User);

        $ResourceCount = count($ResourceIds);

        $this->DB->Query(
            "INSERT INTO VisibleResourceCounts "
            ."(SchemaId, UserClass, ValueId, ResourceCount) "
            ."VALUES ("
            .intval($this->SchemaId).","
            ."'".addslashes($UserClass)."',"
            .intval($ValueId).","
            .$ResourceCount.")");
    }

    /**
    * Get the total number of released resources in the collection
    * @return int The total number of released resources.
    */
    public function GetReleasedResourceTotal()
    {
        return $this->DB->Query("
                SELECT COUNT(*) AS ResourceTotal
                FROM Resources
                WHERE ResourceId > 0
                AND ReleaseFlag = 1
                AND SchemaId = ".intval($this->SchemaId),
                "ResourceTotal");
    }

    /**
    * Get the total number of resources in the collection, even if they are
    * not released.
    * @return int The total number of resources.
    */
    public function GetResourceTotal()
    {
        return $this->DB->Query("
                SELECT COUNT(*) AS ResourceTotal
                FROM Resources
                WHERE ResourceId > 0
                AND SchemaId = ".intval($this->SchemaId),
                "ResourceTotal");
    }

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

    private $SchemaId;
    private $CountCache;

    /**
    * Compute a UserClass based on the privilege flags
    * (i.e. PRIV_SYSADMIN, etc) used in the current schema (i.e.,
    * appearing in conditions within ViewingPrivileges).
    * @param CWUser $User User to compute a user class for
    * @return string user class
    */
    private function ComputeUserClass( $User )
    {
        static $ClassCache;

        # if we don't already have a class cache, initialize one
        if (!isset($ClassCache))
        {
            $ClassCache = array();
        }

        # put the anonymous user into their own user class, otherwise
        # use the UserId for a key into the ClassCache
        $UserId = is_null($User->Id()) ? "XX-ANON-XX" : $User->Id();

        # check if we have a cached UserClass for this User
        if (!isset($ClassCache[$UserId]))
        {
            # assemble a list of the privilege flags (PRIV_SYSADMIN,
            # etc) that are checked when evaluating the UserCanView for
            # all fields in this schema
            $RelevantPerms = array();

            $Schema = new MetadataSchema($this->SchemaId);
            foreach ($Schema->GetFields() as $Field)
            {
                $RelevantPerms = array_merge(
                    $RelevantPerms,
                    $Field->ViewingPrivileges()->PrivilegeFlagsChecked() );
            }
            $RelevantPerms = array_unique($RelevantPerms);

            # whittle the list of all privs checked down to just the
            # list of privs that users in this class have
            $PermsInvolved = array();
            foreach ($RelevantPerms as $Perm)
            {
                if ($User->HasPriv($Perm))
                {
                    $PermsInvolved[]= $Perm;
                }
            }

            # generate a string by concatenating all the involved
            # permissions then hashing the result (hashing gives
            # a fixed-size string for storing in the database)
            $ClassCache[$UserId] = md5( implode( "-", $PermsInvolved ) );
        }

        return $ClassCache[$UserId];
    }

    /**
    * List resources where a UserIs or UserIsNot condition changes
    *  viewability from the default viewability for their user class
    *  (for example, the list of resources a users with their privilege flags
    *  would not normally be able to see, but this specific user can
    *  because they are the value of AddedById)
    * @param CWUser $User CWUser object
    * @return Array of ResourceIds
    */
    private function ResourcesWhereUserComparisonsMatterForViewing($User)
    {
        $ResourceIds = array();

        # if we're checking the anonymous user, presume that
        #  nothing will match
        if (is_null($User->Id()))
        {
            return $ResourceIds;
        }

        $Schema = new MetadataSchema($this->SchemaId);

        # for each comparison type
        foreach (array("==", "!=") as $ComparisonType)
        {
            # iterate through all the fields in the schema,
            #  constructing a list of the User fields implicated
            #  in comparisons of the desired type
            $UserComparisonFields = array();
            foreach ($Schema->GetFields() as $Field)
            {
                $UserComparisonFields = array_merge(
                    $UserComparisonFields,
                    $Field->ViewingPrivileges()->FieldsWithUserComparisons(
                        $ComparisonType) );
            }
            $UserComparisonFields = array_unique($UserComparisonFields);

            # if we have any fields to check
            if (count($UserComparisonFields) > 0 )
            {
                # query the database for resources where one or more of the
                # user comparisons will be satisfied
                $SqlOp = ($ComparisonType == "==") ? "= " : "!= ";
                $DB = new Database();
                $DB->Query("SELECT R.ResourceId as ResourceId FROM ".
                           "Resources R, ResourceUserInts RU WHERE ".
                           "R.SchemaId = ".$this->SchemaId." AND ".
                           "R.ResourceId = RU.ResourceId AND ".
                           "RU.UserId ".$SqlOp.$User->Id()." AND ".
                           "RU.FieldId IN (".implode(",", $UserComparisonFields).")");
                $Result = $DB->FetchColumn("ResourceId");

                # merge those resources into our results
                $ResourceIds = array_merge(
                    $ResourceIds,
                    $Result);
            }
        }

        return array_unique($ResourceIds);
    }
}
