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