CWIS Developer Documentation
ResourceFactory.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: ResourceFactory.php
4 #
5 # Part of the Collection Workflow Integration System (CWIS)
6 # Copyright 2011-2013 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu/cwis/
8 #
9 
14 {
15  # ---- PUBLIC INTERFACE --------------------------------------------------
16 
22  public function __construct($SchemaId = MetadataSchema::SCHEMAID_DEFAULT)
23  {
24  # save schema ID
25  $this->SchemaId = $SchemaId;
26 
27  # set up resource count cache
28  $this->CountCache = array();
29 
30  # set up item factory base class
31  parent::__construct("Resource", "Resources", "ResourceId", NULL, FALSE,
32  "SchemaId = ".intval($this->SchemaId));
33  }
34 
40  public function DuplicateResource($ResourceId)
41  {
42  # check that resource to be duplicated exists
43  if (!Resource::ItemExists($ResourceId))
44  {
45  throw new InvalidArgumentException(
46  "No resource found with specified ID (".$ResourceId.").");
47  }
48 
49  # create new target resource
50  $DstResource = Resource::Create($this->SchemaId);
51 
52  # load up resource to duplicate
53  $SrcResource = new Resource($ResourceId);
54 
55  # for each metadata field
56  $Schema = new MetadataSchema($this->SchemaId);
57  $Fields = $Schema->GetFields();
58  foreach ($Fields as $Field)
59  {
60  if ($Field->CopyOnResourceDuplication())
61  {
62  $NewValue = $SrcResource->GetByField($Field, TRUE);
63 
64  # clear default value from destination resource that is
65  # set when creating a new resource
66  $DstResource->ClearByField($Field);
67 
68  # copy value from source resource to destination resource
69  $DstResource->SetByField($Field, $NewValue);
70  }
71  }
72 
73  # return new resource to caller
74  return $DstResource;
75  }
76 
83  public function ClearQualifier($ObjectOrId, $NewObjectOrId = NULL)
84  {
85  # sanitize qualifier ID or retrieve from object
86  $QualifierId = is_object($ObjectOrId)
87  ? $ObjectOrId->Id() : intval($ObjectOrId);
88 
89  # if new qualifier passed in
90  if ($NewObjectOrId !== NULL)
91  {
92  # sanitize qualifier ID to change to or retrieve it from object
93  $NewQualifierIdVal = is_object($NewObjectOrId)
94  ? $NewObjectOrId->Id() : intval($NewObjectOrId);
95  }
96  else
97  {
98  # qualifier should be cleared
99  $NewQualifierIdVal = "NULL";
100  }
101 
102  # for each metadata field
103  $Schema = new MetadataSchema($this->SchemaId);
104  $Fields = $Schema->GetFields();
105  foreach ($Fields as $Field)
106  {
107  # if field uses qualifiers and uses item-level qualifiers
108  $QualColName = $Field->DBFieldName()."Qualifier";
109  if ($Field->UsesQualifiers()
110  && $Field->HasItemLevelQualifiers()
111  && $this->DB->FieldExists("Resources", $QualColName))
112  {
113  # set all occurrences to new qualifier value
114  $this->DB->Query("UPDATE Resources"
115  ." SET ".$QualColName." = ".$NewQualifierIdVal.""
116  ." WHERE ".$QualColName." = '".$QualifierId."'"
117  ." AND SchemaId = ".intval($this->SchemaId));
118  }
119  }
120 
121  # clear or change qualifier association with controlled names
122  # (NOTE: this should probably be done in a controlled name factory object)
123  $this->DB->Query("UPDATE ControlledNames"
124  ." SET QualifierId = ".$NewQualifierIdVal
125  ." WHERE QualifierId = '".$QualifierId."'");
126 
127  # clear or change qualifier association with classifications
128  # (NOTE: this should probably be done in a classification factory object)
129  $this->DB->Query("UPDATE Classifications"
130  ." SET QualifierId = ".$NewQualifierIdVal
131  ." WHERE QualifierId = '".$QualifierId."'");
132  }
133 
138  public function GetRatedResourceCount()
139  {
140  return $this->DB->Query(
141  "SELECT COUNT(DISTINCT ResourceId) AS ResourceCount"
142  ." FROM ResourceRatings",
143  "ResourceCount");
144  }
145 
150  public function GetRatedResourceUserCount()
151  {
152  return $this->DB->Query(
153  "SELECT COUNT(DISTINCT UserId) AS UserCount"
154  ." FROM ResourceRatings",
155  "UserCount");
156  }
157 
168  $Count = 10, $Offset = 0, $MaxDaysToGoBack = 90)
169  {
170  # assume that no resources will be found
171  $Resources = array();
172 
173  # calculate cutoff date for resources
174  $CutoffDate = date("Y-m-d H:i:s", strtotime($MaxDaysToGoBack." days ago"));
175 
176  # query for resource IDs
177  $this->DB->Query("SELECT ResourceId FROM Resources WHERE"
178  ." DateOfRecordRelease > '".$CutoffDate."'"
179  ." AND ReleaseFlag = 1"
180  ." AND ResourceId >= 0"
181  ." AND SchemaId = ".intval($this->SchemaId)
182  ." ORDER BY DateOfRecordRelease DESC, DateOfRecordCreation DESC"
183  ." LIMIT ".intval($Offset).", ".intval($Count));
184  $ResourceIds = $this->DB->FetchColumn("ResourceId");
185 
186  # for each resource ID found
187  foreach ($ResourceIds as $ResourceId)
188  {
189  # load resource and add to list of found resources
190  $Resources[$ResourceId] = new Resource($ResourceId);
191  }
192 
193  # return found resources to caller
194  return $Resources;
195  }
196 
205  public function GetResourceIdsSortedBy($FieldId, $Ascending = TRUE, $Limit = NULL)
206  {
207  # assume no resources will be found
208  $ResourceIds = array();
209 
210  # get field
211  $Schema = new MetadataSchema($this->SchemaId);
212  $Field = $Schema->GetField($FieldId);
213 
214  # if field was found
215  if ($Field != NULL)
216  {
217  # construct query based on field type
218  switch ($Field->Type())
219  {
223  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
224  ." FROM Resources WHERE "
225  .$Field->DBFieldName()." IS NOT NULL"
226  ." AND LENGTH(LTRIM(RTRIM(".$Field->DBFieldName()."))) > 0"
227  ." AND SchemaId = ".intval($this->SchemaId),
228  "ResourceCount");
229  if ($Count > 0)
230  {
231  $Query = "SELECT ResourceId FROM Resources"
232  ." WHERE SchemaId = ".intval($this->SchemaId)
233  ." ORDER BY ".$Field->DBFieldName()
234  .($Ascending ? " ASC" : " DESC");
235  }
236  break;
237 
240  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
241  ." FROM Resources WHERE "
242  .$Field->DBFieldName()." IS NOT NULL"
243  ." AND SchemaId = ".intval($this->SchemaId),
244  "ResourceCount");
245  if ($Count > 0)
246  {
247  $Query = "SELECT ResourceId FROM Resources"
248  ." WHERE SchemaId = ".intval($this->SchemaId)
249  ." ORDER BY ".$Field->DBFieldName()
250  .($Ascending ? " ASC" : " DESC");
251  }
252  break;
253 
255  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
256  ." FROM Resources WHERE "
257  .$Field->DBFieldName()."Begin IS NOT NULL"
258  ." AND SchemaId = ".intval($this->SchemaId),
259  "ResourceCount");
260  if ($Count > 0)
261  {
262  $Query = "SELECT ResourceId FROM Resources"
263  ." WHERE SchemaId = ".intval($this->SchemaId)
264  ." ORDER BY ".$Field->DBFieldName()."Begin"
265  .($Ascending ? " ASC" : " DESC");
266  }
267  break;
268  }
269 
270  # if appropriate query was found
271  if (isset($Query))
272  {
273  # if limited number of results were requested
274  if ($Limit !== NULL)
275  {
276  # add limit to query
277  $Query .= " LIMIT ".intval($Limit);
278  }
279 
280  # perform query and retrieve resource IDs
281  $this->DB->Query($Query);
282  $ResourceIds = $this->DB->FetchColumn("ResourceId");
283  }
284  }
285 
286  # return resource IDs to caller
287  return $ResourceIds;
288  }
289 
296  public function FilterNonViewableResources($ResourceIds, $User)
297  {
298  $DB = new Database();
299  $Schema = new MetadataSchema($this->SchemaId);
300 
301  # compute this user's class
302  $UserClass = $this->ComputeUserClass($User);
303 
304  # generate an array where the keys are ResourceIds affected by
305  # user comparisons for the current user
306  $UserComparisonsRIDs = array_flip(
307  $this->ResourcesWhereUserComparisonsMatterForViewing($User));
308 
309  # grab all the ResourceIds for this user class
310  $DB->Query("SELECT ResourceId, CanView FROM UserPermsCache WHERE"
311  ." UserClass='".$UserClass."'");
312 
313  # filter out those not requested
314  $Cache = array_intersect_key(
315  $DB->FetchColumn("CanView", "ResourceId"),
316  array_flip($ResourceIds) );
317 
318  # figure out which resources we didn't have cached values for
319  # and iterate over those
320  $MissingIds = array_diff( $ResourceIds, array_keys($Cache) );
321  foreach ($MissingIds as $Id)
322  {
323  # evaluate perms for this resource
324  $Resource = new Resource($Id);
325  $CanView = $Resource->UserCanView($User, FALSE);
326 
327  # if this is a result we can cache, do so
328  if ( !isset($UserComparisonRIDs[$Id]) )
329  {
330  $this->DB->Query(
331  "INSERT INTO UserPermsCache (ResourceId, UserClass, CanView) "
332  ."VALUES (".$Id.",'".$UserClass."',".($CanView?"1":"0").")");
333  }
334 
335  $Cache[$Id] = $CanView;
336  }
337 
338  # apply schema permissions hooks to all our values
339  foreach (array_keys($Cache) as $Id)
340  {
341  $SignalResult = $GLOBALS["AF"]->SignalEvent(
342  "EVENT_RESOURCE_VIEW_PERMISSION_CHECK",
343  array(
344  "Resource" => $Id,
345  "User" => $User,
346  "CanView" => $Cache[$Id],
347  "Schema" => $Schema, ));
348  $Cache[$Id] = $SignalResult["CanView"];
349  }
350 
351  # filter out the non-viewable resources, preserving the order
352  # of resources
353  $Result = array();
354  foreach ($ResourceIds as $ResourceId)
355  {
356  if ($Cache[$ResourceId])
357  {
358  $Result[]= $ResourceId;
359  }
360  }
361 
362  # return the viewable ResourceIds
363  return $Result;
364  }
365 
369  public function ClearViewingPermsCache()
370  {
371  $DB = new Database();
372  $DB->Query("DELETE FROM UserPermsCache");
373  }
374 
381  public function GetTimestampOfLastResourceModification($OnlyReleasedResources = TRUE)
382  {
383  $LastChangeDate = $this->DB->Query(
384  "SELECT MAX(DateLastModified) AS LastChangeDate"
385  ." FROM Resources"
386  ." WHERE SchemaId = ".intval($this->SchemaId)
387  .($OnlyReleasedResources ? " AND ReleaseFlag = 1" : ""),
388  "LastChangeDate");
389  return ($LastChangeDate ? strtotime($LastChangeDate) : NULL);
390  }
391 
397  public function GetPossibleFieldNames()
398  {
399  # retrieve field names from schema
400  $FieldNames = array();
401  $Schema = new MetadataSchema($this->SchemaId);
402  $Fields = $Schema->GetFields();
403  foreach ($Fields as $Field)
404  {
405  $FieldNames[$Field->Id()] = $Field->Name();
406  }
407 
408  # return field names to caller
409  return $FieldNames;
410  }
411 
424  public function GetMatchingResources(
425  $ValuesToMatch, $AllRequired=TRUE, $ReturnObjects=TRUE)
426  {
427  # start out assuming we won't find any resources
428  $Resources = array();
429 
430  $LinkingTerm = "";
431  $Condition = "";
432 
433  # for each value
434  $Schema = new MetadataSchema($this->SchemaId);
435  $Fields = $Schema->GetFields();
436 
437  foreach ($ValuesToMatch as $FieldId => $Value)
438  {
439  # retrieve metadata field ID if not supplied
440  if (!is_numeric($FieldId))
441  {
442  $FieldId = $Schema->GetFieldIdByName($FieldId);
443  }
444 
445  # if we're attempting to search a field that isn't in our schema,
446  # throw an exception
447  if (!isset($Fields[$FieldId]))
448  {
449  throw new Exception(
450  "Attempt to match values against a field "
451  ."that doesn't exist in this schema");
452  }
453 
454  switch ($Fields[$FieldId]->Type())
455  {
463  $DBFname = $Fields[$FieldId]->DBFieldName();
464  # add comparison to condition
465  if ($Value == "NULL")
466  {
467  $Condition .= $LinkingTerm."("
468  .$DBFname." IS NULL OR ".$DBFname." = '')";
469  }
470  else
471  {
472  $Condition .= $LinkingTerm.$DBFname.
473  " = '".addslashes($Value)."'";
474  }
475  break;
476 
478  $DBFname = $Fields[$FieldId]->DBFieldName();
479 
480  if ($Value == "NULL")
481  {
482  $Condition .= $LinkingTerm."("
483  .$DBFname."X IS NULL AND "
484  .$DBFname."Y IS NULL)";
485  }
486  else
487  {
488  $Vx = addslashes($Value["X"]);
489  $Vy = addslashes($value["Y"]);
490 
491  $Condition .= $LinkingTerm."("
492  .$DBFname."X = '".$Vx."' AND "
493  .$DBFname."Y = '".$Vy."')";
494  }
495  break;
496 
498  $TgtValues = array();
499  if (is_object($Value))
500  {
501  $TgtValues[]= $Value->Id();
502  }
503  elseif (is_numeric($Value))
504  {
505  $TgtValues[]= $Value;
506  }
507  elseif (is_array($Value))
508  {
509  foreach ($Value as $UserId => $UserNameOrObject)
510  {
511  $TgtValues[]= $UserId;
512  }
513  }
514 
515  # if no users were specified
516  if (!count($TgtValues))
517  {
518  # return no results (nothing matches nothing)
519  return array();
520  }
521  else
522  {
523  # add conditional to match specified users
524  $Condition .= $LinkingTerm."("
525  ."ResourceId IN (SELECT ResourceId FROM "
526  ."ResourceUserInts WHERE FieldId=".intval($FieldId)
527  ." AND UserId IN ("
528  .implode(",", $TgtValues).")) )";
529  }
530  break;
531 
532  default:
533  throw new Exception("Unsupported field type");
534  }
535 
536  $LinkingTerm = $AllRequired ? " AND " : " OR ";
537  }
538 
539  # if there were valid conditions
540  if (strlen($Condition))
541  {
542  # build query statment
543  $Query = "SELECT ResourceId FROM Resources WHERE (".$Condition
544  .") AND SchemaId = ".intval($this->SchemaId);
545 
546  # execute query to retrieve matching resource IDs
547  $this->DB->Query($Query);
548  $ResourceIds = $this->DB->FetchColumn("ResourceId");
549 
550  if ($ReturnObjects)
551  {
552  # retrieve resource objects
553  foreach ($ResourceIds as $Id)
554  {
555  $Resources[$Id] = new Resource($Id);
556  }
557  }
558  else
559  {
560  $Resources = $ResourceIds;
561  }
562  }
563 
564  # return any resources found to caller
565  return $Resources;
566  }
567 
576  public function AssociatedVisibleResourceCount($ValueId, $User)
577  {
578  # if the specified user is matched by any UserIs or UserIsNot
579  # privset conditions for any resources, then put them in a class
580  # by themselves
581  $UserClass = count($this->ResourcesWhereUserComparisonsMatterForViewing($User))
582  ? "UID_".$User->Id() :
583  $this->ComputeUserClass($User);
584 
585  # if we haven't loaded any cached values, do so now
586  if (!isset($this->CountCache[$UserClass]))
587  {
588  $this->DB->Query(
589  "SELECT ResourceCount, ValueId FROM "
590  ."VisibleResourceCounts WHERE "
591  ."SchemaId=".intval($this->SchemaId)
592  ." AND UserClass='".addslashes($UserClass)."'");
593 
594  $this->CountCache[$UserClass] = $this->DB->FetchColumn(
595  "ResourceCount", "ValueId");
596  }
597 
598  # if we don't have a cached value for this class,
599  # queue a background update and return -1
600  if (!isset($this->CountCache[$UserClass][$ValueId]))
601  {
602  $GLOBALS["AF"]->QueueUniqueTask(
603  array($this, "UpdateAssociatedVisibleResourceCount"),
604  array($ValueId, $User->Id() ) );
605 
606  return -1;
607  }
608 
609  # owtherwise, return the cached data
610  return $this->CountCache[$UserClass][$ValueId];
611  }
612 
620  $ValueId, $UserId)
621  {
622  $User = new CWUser($UserId);
623 
624  # if the specified user is matched by any UserIs or UserIsNot
625  # privset conditions for any resources, then put them in a class
626  # by themselves
627  $UserClass = count($this->ResourcesWhereUserComparisonsMatterForViewing($User))
628  ? "UID_".$User->Id() :
629  $this->ComputeUserClass($User);
630 
631  $this->DB->Query(
632  "SELECT ResourceId FROM ResourceNameInts "
633  ."WHERE ControlledNameId=".intval($ValueId) );
634  $ResourceIds = $this->DB->FetchColumn("ResourceId");
635 
636  $ResourceIds = $this->FilterNonViewableResources(
637  $ResourceIds, $User);
638 
639  $ResourceCount = count($ResourceIds);
640 
641  $this->DB->Query(
642  "INSERT INTO VisibleResourceCounts "
643  ."(SchemaId, UserClass, ValueId, ResourceCount) "
644  ."VALUES ("
645  .intval($this->SchemaId).","
646  ."'".addslashes($UserClass)."',"
647  .intval($ValueId).","
648  .$ResourceCount.")");
649  }
650 
655  public function GetReleasedResourceTotal()
656  {
657  return $this->DB->Query("
658  SELECT COUNT(*) AS ResourceTotal
659  FROM Resources
660  WHERE ResourceId > 0
661  AND ReleaseFlag = 1
662  AND SchemaId = ".intval($this->SchemaId),
663  "ResourceTotal");
664  }
665 
671  public function GetResourceTotal()
672  {
673  return $this->DB->Query("
674  SELECT COUNT(*) AS ResourceTotal
675  FROM Resources
676  WHERE ResourceId > 0
677  AND SchemaId = ".intval($this->SchemaId),
678  "ResourceTotal");
679  }
680 
681  # ---- PRIVATE INTERFACE -------------------------------------------------
682 
683  private $SchemaId;
684  private $CountCache;
685 
693  private function ComputeUserClass( $User )
694  {
695  static $ClassCache;
696 
697  # if we don't already have a class cache, initialize one
698  if (!isset($ClassCache))
699  {
700  $ClassCache = array();
701  }
702 
703  # put the anonymous user into their own user class, otherwise
704  # use the UserId for a key into the ClassCache
705  $UserId = is_null($User->Id()) ? "XX-ANON-XX" : $User->Id();
706 
707  # check if we have a cached UserClass for this User
708  if (!isset($ClassCache[$UserId]))
709  {
710  # assemble a list of the privilege flags (PRIV_SYSADMIN,
711  # etc) that are checked when evaluating the UserCanView for
712  # all fields in this schema
713  $RelevantPerms = array();
714 
715  $Schema = new MetadataSchema($this->SchemaId);
716  foreach ($Schema->GetFields() as $Field)
717  {
718  $RelevantPerms = array_merge(
719  $RelevantPerms,
720  $Field->ViewingPrivileges()->PrivilegeFlagsChecked() );
721  }
722  $RelevantPerms = array_unique($RelevantPerms);
723 
724  # whittle the list of all privs checked down to just the
725  # list of privs that users in this class have
726  $PermsInvolved = array();
727  foreach ($RelevantPerms as $Perm)
728  {
729  if ($User->HasPriv($Perm))
730  {
731  $PermsInvolved[]= $Perm;
732  }
733  }
734 
735  # generate a string by concatenating all the involved
736  # permissions then hashing the result (hashing gives
737  # a fixed-size string for storing in the database)
738  $ClassCache[$UserId] = md5( implode( "-", $PermsInvolved ) );
739  }
740 
741  return $ClassCache[$UserId];
742  }
743 
753  private function ResourcesWhereUserComparisonsMatterForViewing($User)
754  {
755  $ResourceIds = array();
756 
757  # if we're checking the anonymous user, presume that
758  # nothing will match
759  if (is_null($User->Id()))
760  {
761  return $ResourceIds;
762  }
763 
764  $Schema = new MetadataSchema($this->SchemaId);
765 
766  # for each comparison type
767  foreach (array("==", "!=") as $ComparisonType)
768  {
769  # iterate through all the fields in the schema,
770  # constructing a list of the User fields implicated
771  # in comparisons of the desired type
772  $UserComparisonFields = array();
773  foreach ($Schema->GetFields() as $Field)
774  {
775  $UserComparisonFields = array_merge(
776  $UserComparisonFields,
777  $Field->ViewingPrivileges()->FieldsWithUserComparisons(
778  $ComparisonType) );
779  }
780  $UserComparisonFields = array_unique($UserComparisonFields);
781 
782  # if we have any fields to check
783  if (count($UserComparisonFields) > 0 )
784  {
785  # query the database for resources where one or more of the
786  # user comparisons will be satisfied
787  $SqlOp = ($ComparisonType == "==") ? "= " : "!= ";
788  $DB = new Database();
789  $DB->Query("SELECT R.ResourceId as ResourceId FROM ".
790  "Resources R, ResourceUserInts RU WHERE ".
791  "R.SchemaId = ".$this->SchemaId." AND ".
792  "R.ResourceId = RU.ResourceId AND ".
793  "RU.UserId ".$SqlOp.$User->Id()." AND ".
794  "RU.FieldId IN (".implode(",", $UserComparisonFields).")");
795  $Result = $DB->FetchColumn("ResourceId");
796 
797  # merge those resources into our results
798  $ResourceIds = array_merge(
799  $ResourceIds,
800  $Result);
801  }
802  }
803 
804  return array_unique($ResourceIds);
805  }
806 }
GetRatedResourceUserCount()
Return number of users who have rated resources.
Metadata schema (in effect a Factory class for MetadataField).
SQL database abstraction object with smart query caching.
Definition: Database.php:22
GetTimestampOfLastResourceModification($OnlyReleasedResources=TRUE)
Get date/time of when last a resource was modified.
GetResourceIdsSortedBy($FieldId, $Ascending=TRUE, $Limit=NULL)
Get resource IDs sorted by specified field.
ClearViewingPermsCache()
Clear the cache of viewable resources.
UpdateAssociatedVisibleResourceCount($ValueId, $UserId)
Update the count of resources associated with a ControlledName that are visible to a specified user...
GetRecentlyReleasedResources($Count=10, $Offset=0, $MaxDaysToGoBack=90)
Get resources sorted by descending Date of Record Release, with Date of Record Creation as the second...
AssociatedVisibleResourceCount($ValueId, $User)
Return the number of resources visible to a specified user that have a given ControlledName value set...
FilterNonViewableResources($ResourceIds, $User)
Filter a list of resources leaving only those viewable by a specified user.
ClearQualifier($ObjectOrId, $NewObjectOrId=NULL)
Clear or change specific qualifier for all resources.
Represents a "resource" in CWIS.
Definition: Resource.php:13
GetPossibleFieldNames()
Get possible field names for resources.
GetReleasedResourceTotal()
Get the total number of released resources in the collection.
GetMatchingResources($ValuesToMatch, $AllRequired=TRUE, $ReturnObjects=TRUE)
Find resources with values that match those specified.
__construct($SchemaId=MetadataSchema::SCHEMAID_DEFAULT)
Class constructor.
static Create($SchemaId)
Create a new resource.
Definition: Resource.php:47
Common factory class for item manipulation.
Definition: ItemFactory.php:17
static ItemExists($Id)
Check whether an item exists with the specified ID.
Definition: Item.php:89
GetRatedResourceCount()
Return number of resources that have ratings.
Factory for Resource objects.
CWIS-specific user class.
Definition: CWUser.php:13
GetResourceTotal()
Get the total number of resources in the collection, even if they are not released.
DuplicateResource($ResourceId)
Duplicate the specified resource and return to caller.