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
25  $this->SchemaId = $SchemaId;
26  $this->Schema = new MetadataSchema($this->SchemaId);
27 
28  # set up item factory base class
29  parent::__construct("Resource", "Resources", "ResourceId", NULL, FALSE,
30  "SchemaId = ".intval($this->SchemaId));
31  }
32 
37  public function Schema()
38  {
39  return $this->Schema;
40  }
41 
47  public function DuplicateResource($ResourceId)
48  {
49  # check that resource to be duplicated exists
50  if (!Resource::ItemExists($ResourceId))
51  {
52  throw new InvalidArgumentException(
53  "No resource found with specified ID (".$ResourceId.").");
54  }
55 
56  # create new target resource
57  $DstResource = Resource::Create($this->SchemaId);
58 
59  # load up resource to duplicate
60  $SrcResource = new Resource($ResourceId);
61 
62  # for each metadata field
63  $Fields = $this->Schema->GetFields();
64  foreach ($Fields as $Field)
65  {
66  if ($Field->CopyOnResourceDuplication())
67  {
68  $NewValue = $SrcResource->GetByField($Field, TRUE);
69 
70  # clear default value from destination resource that is
71  # set when creating a new resource
72  $DstResource->ClearByField($Field);
73 
74  # copy value from source resource to destination resource
75  $DstResource->SetByField($Field, $NewValue);
76  }
77  }
78 
79  # return new resource to caller
80  return $DstResource;
81  }
82 
95  public function ImportResourcesFromXmlFile($FileName)
96  {
97  # open file
98  $In = new XMLReader();
99  $Result = $In->open($FileName);
100 
101  # throw exception if file could not be opened
102  if ($Result === FALSE)
103  {
104  throw new Exception("Unable to open file: ".$FileName);
105  }
106 
107  # load possible tag names
108  $PossibleTags = array();
109 
110  $Fields = $this->Schema->GetFields();
111  foreach ($Fields as $FieldId => $Field)
112  {
113  $NormalizedName = preg_replace(
114  "/[^A-Za-z0-9]/", "", $Field->Name());
115  $PossibleTags[$NormalizedName] = $Field;
116  }
117 
118  # arrays to hold ControlledName and Classification factories
119  $CNFacts = array();
120  $CFacts = array();
121 
122  # while XML left to read
123  $NewResourceIds = array();
124  while ($In->read())
125  {
126  # if new element
127  if ($In->nodeType == XMLReader::ELEMENT)
128  {
129  # if node indicates start of resource
130  if ($In->name === "Resource")
131  {
132  # if we already had a resource make it non-temporary
133  if (isset($Resource))
134  {
135  $Resource->IsTempResource(FALSE);
136  $NewResourceIds[] = $Resource->Id();
137  }
138 
139  # create a new resource
140  $Resource = Resource::Create($this->SchemaId);
141  }
142  # else if node is in list of possible tags
143  elseif (array_key_exists($In->name, $PossibleTags))
144  {
145  # if we have a current resource
146  if (isset($Resource))
147  {
148  # retrieve field and value
149  $DBFieldName = $In->name;
150  $Field = $PossibleTags[$DBFieldName];
151  $In->read();
152  $Value = $In->value;
153  $In->read();
154 
155  # set value in resource based on field type
156  switch ($Field->Type())
157  {
164  $Resource->Set($Field, $Value);
165  break;
166 
168  $Resource->Set($Field,
169  (strtoupper($Value) == "TRUE") ? TRUE : FALSE);
170  break;
171 
174  if (!isset($CNFacts[$Field->Id()]))
175  {
176  $CNFacts[$Field->Id()] = $Field->GetFactory();
177  }
178 
179  $CName = $CNFacts[$Field->Id()]->GetItemByName($Value);
180  if ($CName === NULL)
181  {
182  $CNFacts[$Field->Id()]->ClearCaches();
183  $CName = new ControlledName(
184  NULL, $Value, $Field->Id());
185  }
186  $Resource->Set($Field, $CName);
187  break;
188 
190  if (!isset($CFacts[$Field->Id()]))
191  {
192  $CFacts[$Field->Id()] = $Field->GetFactory();
193  }
194 
195  $Class = $CFacts[$Field->Id()]->GetItemByName($Value);
196  if ($Class === NULL)
197  {
198  $CFacts[$Field->Id()]->ClearCaches();
199  $Class = Classification::Create($Value, $Field->Id());
200  }
201  $Resource->Set($Field, $Class);
202  break;
203 
205  list($Point["X"], $Point["Y"]) = explode(",", $Value);
206  $Resource->Set($Field, $Point);
207  break;
208 
210  if (preg_match("/^[0-9]+\$/", $Value))
211  {
212  $Value = intval($Value);
213  }
214  $Resource->Set($Field, $Value);
215  break;
216 
220  break;
221 
222  default:
223  break;
224  }
225  }
226  }
227  }
228  }
229 
230  # make final resource (if any) non-temporary
231  if (isset($Resource))
232  {
233  $Resource->IsTempResource(FALSE);
234  $NewResourceIds[] = $Resource->Id();
235  }
236 
237  # close file
238  $In->close();
239 
240  # report to caller what resources were added
241  return $NewResourceIds;
242  }
243 
250  public function ClearQualifier($ObjectOrId, $NewObjectOrId = NULL)
251  {
252  # sanitize qualifier ID or retrieve from object
253  $QualifierId = is_object($ObjectOrId)
254  ? $ObjectOrId->Id() : intval($ObjectOrId);
255 
256  # if new qualifier passed in
257  if ($NewObjectOrId !== NULL)
258  {
259  # sanitize qualifier ID to change to or retrieve it from object
260  $NewQualifierIdVal = is_object($NewObjectOrId)
261  ? $NewObjectOrId->Id() : intval($NewObjectOrId);
262  }
263  else
264  {
265  # qualifier should be cleared
266  $NewQualifierIdVal = "NULL";
267  }
268 
269  # for each metadata field
270  $Fields = $this->Schema->GetFields();
271  foreach ($Fields as $Field)
272  {
273  # if field uses qualifiers and uses item-level qualifiers
274  $QualColName = $Field->DBFieldName()."Qualifier";
275  if ($Field->UsesQualifiers()
276  && $Field->HasItemLevelQualifiers()
277  && $this->DB->FieldExists("Resources", $QualColName))
278  {
279  # set all occurrences to new qualifier value
280  $this->DB->Query("UPDATE Resources"
281  ." SET ".$QualColName." = ".$NewQualifierIdVal.""
282  ." WHERE ".$QualColName." = '".$QualifierId."'"
283  ." AND SchemaId = ".intval($this->SchemaId));
284  }
285  }
286 
287  # clear or change qualifier association with controlled names
288  # (NOTE: this should probably be done in a controlled name factory object)
289  $this->DB->Query("UPDATE ControlledNames"
290  ." SET QualifierId = ".$NewQualifierIdVal
291  ." WHERE QualifierId = '".$QualifierId."'");
292 
293  # clear or change qualifier association with classifications
294  # (NOTE: this should probably be done in a classification factory object)
295  $this->DB->Query("UPDATE Classifications"
296  ." SET QualifierId = ".$NewQualifierIdVal
297  ." WHERE QualifierId = '".$QualifierId."'");
298  }
299 
304  public function GetRatedResourceCount()
305  {
306  return $this->DB->Query(
307  "SELECT COUNT(DISTINCT ResourceId) AS ResourceCount"
308  ." FROM ResourceRatings",
309  "ResourceCount");
310  }
311 
316  public function GetRatedResourceUserCount()
317  {
318  return $this->DB->Query(
319  "SELECT COUNT(DISTINCT UserId) AS UserCount"
320  ." FROM ResourceRatings",
321  "UserCount");
322  }
323 
334  $Count = 10, $Offset = 0, $MaxDaysToGoBack = 90)
335  {
336  # assume that no resources will be found
337  $Resources = array();
338 
339  # calculate cutoff date for resources
340  $CutoffDate = date("Y-m-d H:i:s", strtotime($MaxDaysToGoBack." days ago"));
341 
342  # query for resource IDs
343  $this->DB->Query("SELECT ResourceId FROM Resources WHERE"
344  ." DateOfRecordRelease > '".$CutoffDate."'"
345  ." AND ResourceId >= 0"
346  ." AND SchemaId = ".intval($this->SchemaId)
347  ." ORDER BY DateOfRecordRelease DESC, DateOfRecordCreation DESC");
348  $ResourceIds = $this->DB->FetchColumn("ResourceId");
349 
350  # filter out resources that aren't viewable to the public
351  $ResourceIds = $this->FilterNonViewableResources(
352  $ResourceIds, CWUser::GetAnonymousUser() );
353 
354  # subset the results as requested
355  $ResourceIds = array_slice(
356  $ResourceIds, $Offset, $Count);
357 
358  # for each resource ID found
359  foreach ($ResourceIds as $ResourceId)
360  {
361  # load resource and add to list of found resources
362  $Resources[$ResourceId] = new Resource($ResourceId);
363  }
364 
365  # return found resources to caller
366  return $Resources;
367  }
368 
377  public function GetResourceIdsSortedBy($FieldId, $Ascending = TRUE, $Limit = NULL)
378  {
379  # assume no resources will be found
380  $ResourceIds = array();
381 
382  # if field was found
383  if ($this->Schema->FieldExists($FieldId))
384  {
385  $Field = $this->Schema->GetField($FieldId);
386  # construct query based on field type
387  switch ($Field->Type())
388  {
392  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
393  ." FROM Resources WHERE "
394  .$Field->DBFieldName()." IS NOT NULL"
395  ." AND LENGTH(LTRIM(RTRIM(".$Field->DBFieldName()."))) > 0"
396  ." AND SchemaId = ".intval($this->SchemaId),
397  "ResourceCount");
398  if ($Count > 0)
399  {
400  $Query = "SELECT ResourceId FROM Resources"
401  ." WHERE SchemaId = ".intval($this->SchemaId)
402  ." ORDER BY ".$Field->DBFieldName()
403  .($Ascending ? " ASC" : " DESC");
404  }
405  break;
406 
409  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
410  ." FROM Resources WHERE "
411  .$Field->DBFieldName()." IS NOT NULL"
412  ." AND SchemaId = ".intval($this->SchemaId),
413  "ResourceCount");
414  if ($Count > 0)
415  {
416  $Query = "SELECT ResourceId FROM Resources"
417  ." WHERE SchemaId = ".intval($this->SchemaId)
418  ." ORDER BY ".$Field->DBFieldName()
419  .($Ascending ? " ASC" : " DESC");
420  }
421  break;
422 
424  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
425  ." FROM Resources WHERE "
426  .$Field->DBFieldName()."Begin IS NOT NULL"
427  ." AND SchemaId = ".intval($this->SchemaId),
428  "ResourceCount");
429  if ($Count > 0)
430  {
431  $Query = "SELECT ResourceId FROM Resources"
432  ." WHERE SchemaId = ".intval($this->SchemaId)
433  ." ORDER BY ".$Field->DBFieldName()."Begin"
434  .($Ascending ? " ASC" : " DESC");
435  }
436  break;
437  }
438 
439  # if appropriate query was found
440  if (isset($Query))
441  {
442  # if limited number of results were requested
443  if ($Limit !== NULL)
444  {
445  # add limit to query
446  $Query .= " LIMIT ".intval($Limit);
447  }
448 
449  # perform query and retrieve resource IDs
450  $this->DB->Query($Query);
451  $ResourceIds = $this->DB->FetchColumn("ResourceId");
452  }
453  }
454 
455  # return resource IDs to caller
456  return $ResourceIds;
457  }
458 
465  public function FilterNonViewableResources($ResourceIds, $User)
466  {
467  # compute this user's class
468  $UserClass = $this->ComputeUserClass($User);
469 
470  # generate an array where the keys are ResourceIds affected by
471  # user comparisons for the current user
472  $UserComparisonsRIDs = array_flip(
473  $this->ResourcesWhereUserComparisonsMatterForViewing($User));
474 
475  # (Note: We can use the $UserClass without a schema prefix as
476  # a cache key even though User Classes are schema specific
477  # because the values we're caching are ResourceIds. Since the
478  # ResourceIds already imply a schema, there's no ambiguity
479  # regarding which schema was involved when the stored UserClass
480  # was computed.)
481  if (!isset(self::$UserClassPermissionsCache[$UserClass]))
482  {
483  # grab all the ResourceIds for this user class
484  $this->DB->Query("SELECT ResourceId, CanView FROM UserPermsCache WHERE"
485  ." UserClass='".$UserClass."'");
486 
487  self::$UserClassPermissionsCache[$UserClass] = $this->DB->FetchColumn(
488  "CanView", "ResourceId");
489  }
490 
491  # filter out those not requested
492  $Cache = array_intersect_key(
493  self::$UserClassPermissionsCache[$UserClass],
494  array_flip($ResourceIds) );
495 
496  # figure out which resources we didn't have cached values for
497  # and iterate over those
498  $MissingIds = array_diff($ResourceIds, array_keys($Cache));
499 
500  $PerUserKey = $this->SchemaId.".UID_".$User->Id();
501 
502  # batch inserts up into not more than 1000 resources per query
503  $ChunkSize = 1000;
504  $QueryValues = array();
505  foreach ($MissingIds as $Id)
506  {
507  if (isset(self::$PerUserPermissionsCache[$PerUserKey]))
508  {
509  $CanView = self::$PerUserPermissionsCache[$PerUserKey];
510  }
511  else
512  {
513  # evaluate perms for this resource
514  if (Resource::ItemExists($Id))
515  {
516  $Resource = new Resource($Id);
517  $CanView = $Resource->UserCanView($User, FALSE);
518  }
519  else
520  {
521  $CanView = FALSE;
522  }
523 
524  # if this is a result we can cache persistently
525  # (i.e. not affected by user comparisons), do so
526  if (!isset($UserComparisonsRIDs[$Id]))
527  {
528  self::$UserClassPermissionsCache[$UserClass][$Id] = $CanView;
529 
530  # add this to our queue of inserts
531  $QueryValues[]= "(".$Id.",'".$UserClass."',".($CanView?"1":"0").")" ;
532 
533  # if this chunk is full, insert it into the db and clear our queue
534  if (count($QueryValues)>=$ChunkSize)
535  {
536  $this->DB->Query(
537  "INSERT INTO UserPermsCache (ResourceId, UserClass, CanView) "
538  ."VALUES ".implode(",", $QueryValues) );
539  $QueryValues = array();
540  }
541  }
542  else
543  {
544  # this isn't a result we should cache persistently
545  # in the database, but we still want to cache it
546  # within this page load
547  self::$PerUserPermissionsCache[$PerUserKey] = $CanView;
548  }
549 
550  }
551  $Cache[$Id] = $CanView;
552  }
553 
554  # if we have values left to insert, do so
555  if (count($QueryValues))
556  {
557  $this->DB->Query(
558  "INSERT INTO UserPermsCache (ResourceId, UserClass, CanView) "
559  ."VALUES ".implode(",", $QueryValues) );
560  }
561 
562  # if resource view permission check has any handlers that may
563  # modify our cached values
564  if ($GLOBALS["AF"]->IsHookedEvent("EVENT_RESOURCE_VIEW_PERMISSION_CHECK"))
565  {
566  # apply hooked functions to each value
567  foreach (array_keys($Cache) as $Id)
568  {
569  $SignalResult = $GLOBALS["AF"]->SignalEvent(
570  "EVENT_RESOURCE_VIEW_PERMISSION_CHECK",
571  array(
572  "Resource" => $Id,
573  "User" => $User,
574  "CanView" => $Cache[$Id],
575  "Schema" => $this->Schema, ));
576  $Cache[$Id] = $SignalResult["CanView"];
577  }
578  }
579 
580  # filter out the non-viewable resources, preserving the order
581  # of resources
582  return array_intersect($ResourceIds,
583  array_keys(array_filter($Cache)) );
584  }
585 
589  public function ClearViewingPermsCache()
590  {
591  $this->DB->Query("DELETE FROM UserPermsCache");
592  }
593 
594 
600  public function GetPossibleFieldNames()
601  {
602  # retrieve field names from schema
603  $FieldNames = array();
604  $Fields = $this->Schema->GetFields();
605  foreach ($Fields as $Field)
606  {
607  $FieldNames[$Field->Id()] = $Field->Name();
608  }
609 
610  # return field names to caller
611  return $FieldNames;
612  }
613 
627  public function GetMatchingResources(
628  $ValuesToMatch, $AllRequired=TRUE, $ReturnObjects=TRUE,
629  $Operator = "==")
630  {
631  # start out assuming we won't find any resources
632  $Resources = array();
633 
634  # fix up equality operator
635  if ($Operator == "==")
636  {
637  $Operator = "=";
638  }
639 
640  $LinkingTerm = "";
641  $Condition = "";
642 
643  # for each value
644  $Fields = $this->Schema->GetFields();
645  foreach ($ValuesToMatch as $FieldId => $Value)
646  {
647  # only equality supported for NULL
648  if ($Operator != "=" && $Value == "NULL")
649  {
650  throw new Exception(
651  "Invalid operator, ".$Operator." not supported for NULL");
652  }
653 
654  # convert supplied FieldId to canonical identifier
656  $FieldId, $this->SchemaId);
657 
658  # check that provided operator is sane
659  switch ($Fields[$FieldId]->Type())
660  {
666  $ValidOps = array("=");
667  break;
668 
670  $ValidOps = array("=", "!=");
671  break;
672 
676  $ValidOps = array("=", "!=", "<", "<=", ">", ">=");
677  break;
678 
679  default:
680  $ValidOps = array();
681  }
682 
683  if (!in_array($Operator, $ValidOps))
684  {
685  throw new Exception("Operator ".$Operator." not supported for "
686  .$Field->TypeAsName()." fields");
687  }
688 
689  # add SQL fragments to Condition as needed
690  switch ($Fields[$FieldId]->Type())
691  {
699  $DBFname = $Fields[$FieldId]->DBFieldName();
700  # add comparison to condition
701  if ($Value == "NULL")
702  {
703  $Condition .= $LinkingTerm."("
704  .$DBFname." IS NULL OR ".$DBFname." = '')";
705  }
706  else
707  {
708  $Condition .= $LinkingTerm.$DBFname." "
709  .$Operator." '".addslashes($Value)."'";
710  }
711  break;
712 
714  $DBFname = $Fields[$FieldId]->DBFieldName();
715 
716  if ($Value == "NULL")
717  {
718  $Condition .= $LinkingTerm."("
719  .$DBFname."X IS NULL AND "
720  .$DBFname."Y IS NULL)";
721  }
722  else
723  {
724  $Vx = addslashes($Value["X"]);
725  $Vy = addslashes($value["Y"]);
726 
727  $Condition .= $LinkingTerm."("
728  .$DBFname."X = '".$Vx."' AND "
729  .$DBFname."Y = '".$Vy."')";
730  }
731  break;
732 
734  $TgtValues = array();
735  if (is_object($Value))
736  {
737  $TgtValues[]= $Value->Id();
738  }
739  elseif (is_numeric($Value))
740  {
741  $TgtValues[]= $Value;
742  }
743  elseif (is_array($Value))
744  {
745  foreach ($Value as $UserId => $UserNameOrObject)
746  {
747  $TgtValues[]= $UserId;
748  }
749  }
750 
751  # if no users were specified
752  if (!count($TgtValues))
753  {
754  # return no results (nothing matches nothing)
755  return array();
756  }
757  else
758  {
759  # add conditional to match specified users
760  $Condition .= $LinkingTerm."("
761  ."ResourceId IN (SELECT ResourceId FROM "
762  ."ResourceUserInts WHERE FieldId=".intval($FieldId)
763  ." AND UserId IN ("
764  .implode(",", $TgtValues).")) )";
765  }
766  break;
767 
768  default:
769  throw new Exception("Unsupported field type");
770  }
771 
772  $LinkingTerm = $AllRequired ? " AND " : " OR ";
773  }
774 
775  # if there were valid conditions
776  if (strlen($Condition))
777  {
778  # build query statment
779  $Query = "SELECT ResourceId FROM Resources WHERE (".$Condition
780  .") AND SchemaId = ".intval($this->SchemaId);
781 
782  # execute query to retrieve matching resource IDs
783  $this->DB->Query($Query);
784  $ResourceIds = $this->DB->FetchColumn("ResourceId");
785 
786  if ($ReturnObjects)
787  {
788  # retrieve resource objects
789  foreach ($ResourceIds as $Id)
790  {
791  $Resources[$Id] = new Resource($Id);
792  }
793  }
794  else
795  {
796  $Resources = $ResourceIds;
797  }
798  }
799 
800  # return any resources found to caller
801  return $Resources;
802  }
803 
816  $ValueId, $User, $ForegroundUpdate=FALSE)
817  {
818  # if the specified user is matched by any UserIs or UserIsNot
819  # privset conditions for any resources, then put them in a class
820  # by themselves
821  $UserClass = count($this->ResourcesWhereUserComparisonsMatterForViewing($User))
822  ? "UID_".$User->Id() :
823  $this->ComputeUserClass($User);
824 
825  $CacheKey = $this->SchemaId.".".$UserClass;
826  # if we haven't loaded any cached values, do so now
827  if (!isset(self::$VisibleResourceCountCache[$CacheKey]))
828  {
829  $this->DB->Query(
830  "SELECT ResourceCount, ValueId FROM "
831  ."VisibleResourceCounts WHERE "
832  ."SchemaId=".intval($this->SchemaId)
833  ." AND UserClass='".addslashes($UserClass)."'");
834 
835  self::$VisibleResourceCountCache[$CacheKey] = $this->DB->FetchColumn(
836  "ResourceCount", "ValueId");
837  }
838 
839  # if we don't have a cached value for this class
840  if (!isset(self::$VisibleResourceCountCache[$CacheKey][$ValueId]))
841  {
842  # if we're doing a foreground update
843  if ($ForegroundUpdate)
844  {
845  # run the update callback
847  $ValueId, $User->Id());
848 
849  # grab the newly generated value
850  $NewValue = $this->DB->Query(
851  "SELECT ResourceCount FROM "
852  ."VisibleResourceCounts WHERE "
853  ."SchemaId=".intval($this->SchemaId)
854  ." AND UserClass='".addslashes($UserClass)."' "
855  ." AND ValueId=".intval($ValueId), "ResourceCount");
856 
857  # load it into our local cache
858  self::$VisibleResourceCountCache[$CacheKey][$ValueId] = $NewValue;
859  }
860  else
861  {
862  # otherwise (for background update), queue the update
863  # callback and return -1
864  $GLOBALS["AF"]->QueueUniqueTask(
865  array($this, "UpdateAssociatedVisibleResourceCount"),
866  array($ValueId, $User->Id() ) );
867  return -1;
868  }
869  }
870 
871  # owtherwise, return the cached data
872  return self::$VisibleResourceCountCache[$CacheKey][$ValueId];
873  }
874 
882  $ValueId, $UserId)
883  {
884  $User = new CWUser($UserId);
885 
886  # if the specified user is matched by any UserIs or UserIsNot
887  # privset conditions for any resources, then put them in a class
888  # by themselves
889  $UserClass = count($this->ResourcesWhereUserComparisonsMatterForViewing($User))
890  ? "UID_".$User->Id() :
891  $this->ComputeUserClass($User);
892 
893  $this->DB->Query(
894  "SELECT ResourceId FROM ResourceNameInts "
895  ."WHERE ControlledNameId=".intval($ValueId) );
896  $ResourceIds = $this->DB->FetchColumn("ResourceId");
897 
898  $ResourceIds = $this->FilterNonViewableResources(
899  $ResourceIds, $User);
900 
901  $ResourceCount = count($ResourceIds);
902 
903  $this->DB->Query(
904  "INSERT INTO VisibleResourceCounts "
905  ."(SchemaId, UserClass, ValueId, ResourceCount) "
906  ."VALUES ("
907  .intval($this->SchemaId).","
908  ."'".addslashes($UserClass)."',"
909  .intval($ValueId).","
910  .$ResourceCount.")");
911  }
912 
918  public function GetVisibleResourceCount(CWUser $User)
919  {
920  $ResourceIds = $this->DB->Query(
921  "SELECT ResourceId FROM Resources "
922  ."WHERE ResourceId > 0 AND SchemaId = ".intval($this->SchemaId));
923  $ResourceIds = $this->DB->FetchColumn("ResourceId");
924 
925  $ResourceIds = $this->FilterNonViewableResources(
926  $ResourceIds, $User);
927 
928  return count($ResourceIds);
929  }
930 
931 
936  public function GetReleasedResourceTotal()
937  {
938  return $this->GetVisibleResourceCount(
940  }
941 
947  public function GetResourceTotal()
948  {
949  return $this->DB->Query("
950  SELECT COUNT(*) AS ResourceTotal
951  FROM Resources
952  WHERE ResourceId > 0
953  AND SchemaId = ".intval($this->SchemaId),
954  "ResourceTotal");
955  }
956 
961  public function ClearCaches()
962  {
963  self::$VisibleResourceCountCache = array();
964  self::$UserClassPermissionsCache = array();
965  self::$PerUserPermissionsCache = array();
966  self::$UserClassCache = array();
967  self::$UserComparisonResourceCache = array();
968  self::$UserComparisonFieldCache = array();
969  }
970 
971  # ---- PRIVATE INTERFACE -------------------------------------------------
972 
973  private $Schema;
974  private $SchemaId;
975 
976  # internal caches
977  private static $VisibleResourceCountCache;
978  private static $UserClassPermissionsCache;
979  private static $PerUserPermissionsCache;
980  private static $UserClassCache;
981  private static $UserComparisonResourceCache;
982  private static $UserComparisonFieldCache;
983 
991  private function ComputeUserClass($User)
992  {
993  # put the anonymous user into their own user class, otherwise
994  # use the UserId for a key into the ClassCache
995  $UserId = $User->IsAnonymous() ? "XX-ANON-XX" : $User->Id();
996 
997  $CacheKey = $this->SchemaId.".".$UserId;
998 
999  # check if we have a cached UserClass for this User
1000  if (!isset($this->UserClassCache[$CacheKey]))
1001  {
1002  # assemble a list of the privilege flags (PRIV_SYSADMIN,
1003  # etc) that are checked when evaluating the UserCanView for
1004  # all fields in this schema
1005  $RelevantPerms = array();
1006 
1007  foreach ($this->Schema->GetFields() as $Field)
1008  {
1009  $RelevantPerms = array_merge(
1010  $RelevantPerms,
1011  $Field->ViewingPrivileges()->PrivilegeFlagsChecked() );
1012  }
1013  $RelevantPerms = array_unique($RelevantPerms);
1014 
1015  # whittle the list of all privs checked down to just the
1016  # list of privs that users in this class have
1017  $PermsInvolved = array();
1018  foreach ($RelevantPerms as $Perm)
1019  {
1020  if ($User->HasPriv($Perm))
1021  {
1022  $PermsInvolved[]= $Perm;
1023  }
1024  }
1025 
1026  # generate a string by concatenating all the involved
1027  # permissions then hashing the result (hashing gives
1028  # a fixed-size string for storing in the database)
1029  self::$UserClassCache[$CacheKey] = md5(implode( "-", $PermsInvolved ));
1030  }
1031 
1032  return self::$UserClassCache[$CacheKey];
1033  }
1034 
1044  private function ResourcesWhereUserComparisonsMatterForViewing($User)
1045  {
1046  $ResourceIds = array();
1047 
1048  # if we're checking the anonymous user, presume that
1049  # nothing will match
1050  if ($User->IsAnonymous())
1051  {
1052  return $ResourceIds;
1053  }
1054 
1055  $CacheKey = $this->SchemaId.".".$User->Id();
1056  if (!isset(self::$UserComparisonResourceCache[$CacheKey]))
1057  {
1058  $Schema = new MetadataSchema($this->SchemaId);
1059 
1060  # for each comparison type
1061  foreach (array("==", "!=") as $ComparisonType)
1062  {
1063  $UserComparisonFields = $this->GetUserComparisonFields(
1064  $ComparisonType);
1065 
1066  # if we have any fields to check
1067  if (count($UserComparisonFields) > 0 )
1068  {
1069  # query the database for resources where one or more of the
1070  # user comparisons will be satisfied
1071  $SqlOp = ($ComparisonType == "==") ? "= " : "!= ";
1072  $DB = new Database();
1073  $DB->Query("SELECT R.ResourceId as ResourceId FROM ".
1074  "Resources R, ResourceUserInts RU WHERE ".
1075  "R.SchemaId = ".$this->SchemaId." AND ".
1076  "R.ResourceId = RU.ResourceId AND ".
1077  "RU.UserId ".$SqlOp.$User->Id()." AND ".
1078  "RU.FieldId IN (".implode(",", $UserComparisonFields).")");
1079  $Result = $DB->FetchColumn("ResourceId");
1080 
1081  # merge those resources into our results
1082  $ResourceIds = array_merge(
1083  $ResourceIds,
1084  $Result);
1085  }
1086  }
1087 
1088  self::$UserComparisonResourceCache[$CacheKey] = array_unique($ResourceIds);
1089  }
1090 
1091  return self::$UserComparisonResourceCache[$CacheKey];
1092  }
1093 
1094 
1100  private function GetUserComparisonFields($ComparisonType)
1101  {
1102  $CacheKey = $this->SchemaId.".".$ComparisonType;
1103  if (!isset(self::$UserComparisonFieldCache[$CacheKey]))
1104  {
1105  # iterate through all the fields in the schema,
1106  # constructing a list of the User fields implicated
1107  # in comparisons of the desired type
1108  $UserComparisonFields = array();
1109  foreach ($this->Schema->GetFields() as $Field)
1110  {
1111  $UserComparisonFields = array_merge(
1112  $UserComparisonFields,
1113  $Field->ViewingPrivileges()->FieldsWithUserComparisons(
1114  $ComparisonType) );
1115  }
1116  self::$UserComparisonFieldCache[$CacheKey] =
1117  array_unique($UserComparisonFields);
1118  }
1119 
1120  return self::$UserComparisonFieldCache[$CacheKey];
1121  }
1122 }
GetMatchingResources($ValuesToMatch, $AllRequired=TRUE, $ReturnObjects=TRUE, $Operator="==")
Find resources with values that match those specified.
static GetAnonymousUser()
Get the anonymous user (i.e., the User object that exists when no user is logged in), useful when a permission check needs to know if something should be visible to the general public.
Definition: User.php:1038
AssociatedVisibleResourceCount($ValueId, $User, $ForegroundUpdate=FALSE)
Return the number of resources in this schema that are visible to a specified user and that have a gi...
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
static Create($Name, $FieldId, $ParentId=NULL)
Add new classification to the hierarchy.
GetResourceIdsSortedBy($FieldId, $Ascending=TRUE, $Limit=NULL)
Get resource IDs sorted by specified field.
static GetCanonicalFieldIdentifier($Field, $SchemaId=NULL)
Retrieve canonical identifier for 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...
ImportResourcesFromXmlFile($FileName)
Import resource records from XML file.
Metadata type representing non-hierarchical controlled vocabulary values.
const MDFTYPE_CONTROLLEDNAME
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.
ClearCaches()
Clear internal caches.
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.
Schema()
Get metadata schema associated with this resource factory.
__construct($SchemaId=MetadataSchema::SCHEMAID_DEFAULT)
Class constructor.
static Create($SchemaId)
Create a new resource.
Definition: Resource.php:48
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:162
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.
GetVisibleResourceCount(CWUser $User)
Get the total number of resources visible to a specified user.
DuplicateResource($ResourceId)
Duplicate the specified resource and return to caller.