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