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 
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 = ControlledName::Create($Value, $Field->Id());
184  }
185  $Resource->Set($Field, $CName);
186  break;
187 
189  if (!isset($CFacts[$Field->Id()]))
190  {
191  $CFacts[$Field->Id()] = $Field->GetFactory();
192  }
193 
194  $Class = $CFacts[$Field->Id()]->GetItemByName($Value);
195  if ($Class === NULL)
196  {
197  $CFacts[$Field->Id()]->ClearCaches();
198  $Class = Classification::Create($Value, $Field->Id());
199  }
200  $Resource->Set($Field, $Class);
201  break;
202 
204  list($Point["X"], $Point["Y"]) = explode(",", $Value);
205  $Resource->Set($Field, $Point);
206  break;
207 
209  if (preg_match("/^[0-9]+\$/", $Value))
210  {
211  $Value = intval($Value);
212  }
213  $Resource->Set($Field, $Value);
214  break;
215 
219  break;
220 
221  default:
222  break;
223  }
224  }
225  }
226  }
227  }
228 
229  # make final resource (if any) non-temporary
230  if (isset($Resource))
231  {
232  $Resource->IsTempResource(FALSE);
233  $NewResourceIds[] = $Resource->Id();
234  }
235 
236  # close file
237  $In->close();
238 
239  # report to caller what resources were added
240  return $NewResourceIds;
241  }
242 
249  public function ClearQualifier($ObjectOrId, $NewObjectOrId = NULL)
250  {
251  # sanitize qualifier ID or retrieve from object
252  $QualifierId = is_object($ObjectOrId)
253  ? $ObjectOrId->Id() : intval($ObjectOrId);
254 
255  # if new qualifier passed in
256  if ($NewObjectOrId !== NULL)
257  {
258  # sanitize qualifier ID to change to or retrieve it from object
259  $NewQualifierIdVal = is_object($NewObjectOrId)
260  ? $NewObjectOrId->Id() : intval($NewObjectOrId);
261  }
262  else
263  {
264  # qualifier should be cleared
265  $NewQualifierIdVal = "NULL";
266  }
267 
268  # for each metadata field
269  $Fields = $this->Schema->GetFields();
270  foreach ($Fields as $Field)
271  {
272  # if field uses qualifiers and uses item-level qualifiers
273  $QualColName = $Field->DBFieldName()."Qualifier";
274  if ($Field->UsesQualifiers()
275  && $Field->HasItemLevelQualifiers()
276  && $this->DB->FieldExists("Resources", $QualColName))
277  {
278  # set all occurrences to new qualifier value
279  $this->DB->Query("UPDATE Resources"
280  ." SET ".$QualColName." = ".$NewQualifierIdVal.""
281  ." WHERE ".$QualColName." = '".$QualifierId."'"
282  ." AND SchemaId = ".intval($this->SchemaId));
283  }
284  }
285 
286  # clear or change qualifier association with controlled names
287  # (NOTE: this should probably be done in a controlled name factory object)
288  $this->DB->Query("UPDATE ControlledNames"
289  ." SET QualifierId = ".$NewQualifierIdVal
290  ." WHERE QualifierId = '".$QualifierId."'");
291 
292  # clear or change qualifier association with classifications
293  # (NOTE: this should probably be done in a classification factory object)
294  $this->DB->Query("UPDATE Classifications"
295  ." SET QualifierId = ".$NewQualifierIdVal
296  ." WHERE QualifierId = '".$QualifierId."'");
297  }
298 
303  public function GetRatedResourceCount()
304  {
305  return $this->DB->Query(
306  "SELECT COUNT(DISTINCT ResourceId) AS ResourceCount"
307  ." FROM ResourceRatings",
308  "ResourceCount");
309  }
310 
315  public function GetRatedResourceUserCount()
316  {
317  return $this->DB->Query(
318  "SELECT COUNT(DISTINCT UserId) AS UserCount"
319  ." FROM ResourceRatings",
320  "UserCount");
321  }
322 
333  $Count = 10, $Offset = 0, $MaxDaysToGoBack = 90)
334  {
335  # assume that no resources will be found
336  $Resources = array();
337 
338  # calculate cutoff date for resources
339  $CutoffDate = date("Y-m-d H:i:s", strtotime($MaxDaysToGoBack." days ago"));
340 
341  # query for resource IDs
342  $this->DB->Query("SELECT ResourceId FROM Resources WHERE"
343  ." DateOfRecordRelease > '".$CutoffDate."'"
344  ." AND ResourceId >= 0"
345  ." AND SchemaId = ".intval($this->SchemaId)
346  ." ORDER BY DateOfRecordRelease DESC, DateOfRecordCreation DESC");
347  $ResourceIds = $this->DB->FetchColumn("ResourceId");
348 
349  # filter out resources that aren't viewable to current user
350  $ResourceIds = $this->FilterNonViewableResources(
351  $ResourceIds, $GLOBALS["G_User"] );
352 
353  # subset the results as requested
354  $ResourceIds = array_slice(
355  $ResourceIds, $Offset, $Count);
356 
357  # for each resource ID found
358  foreach ($ResourceIds as $ResourceId)
359  {
360  # load resource and add to list of found resources
361  $Resources[$ResourceId] = new Resource($ResourceId);
362  }
363 
364  # return found resources to caller
365  return $Resources;
366  }
367 
376  public function GetResourceIdsSortedBy($FieldId, $Ascending = TRUE, $Limit = NULL)
377  {
378  # assume no resources will be found
379  $ResourceIds = array();
380 
381  # if field was found
382  if ($this->Schema->FieldExists($FieldId))
383  {
384  $Field = $this->Schema->GetField($FieldId);
385  # construct query based on field type
386  switch ($Field->Type())
387  {
391  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
392  ." FROM Resources WHERE "
393  .$Field->DBFieldName()." IS NOT NULL"
394  ." AND LENGTH(LTRIM(RTRIM(".$Field->DBFieldName()."))) > 0"
395  ." AND SchemaId = ".intval($this->SchemaId),
396  "ResourceCount");
397  if ($Count > 0)
398  {
399  $Query = "SELECT ResourceId FROM Resources"
400  ." WHERE SchemaId = ".intval($this->SchemaId)
401  ." ORDER BY ".$Field->DBFieldName()
402  .($Ascending ? " ASC" : " DESC");
403  }
404  break;
405 
408  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
409  ." FROM Resources WHERE "
410  .$Field->DBFieldName()." IS NOT NULL"
411  ." AND SchemaId = ".intval($this->SchemaId),
412  "ResourceCount");
413  if ($Count > 0)
414  {
415  $Query = "SELECT ResourceId FROM Resources"
416  ." WHERE SchemaId = ".intval($this->SchemaId)
417  ." ORDER BY ".$Field->DBFieldName()
418  .($Ascending ? " ASC" : " DESC");
419  }
420  break;
421 
423  $Count = $this->DB->Query("SELECT COUNT(*) AS ResourceCount"
424  ." FROM Resources WHERE "
425  .$Field->DBFieldName()."Begin IS NOT NULL"
426  ." AND SchemaId = ".intval($this->SchemaId),
427  "ResourceCount");
428  if ($Count > 0)
429  {
430  $Query = "SELECT ResourceId FROM Resources"
431  ." WHERE SchemaId = ".intval($this->SchemaId)
432  ." ORDER BY ".$Field->DBFieldName()."Begin"
433  .($Ascending ? " ASC" : " DESC");
434  }
435  break;
436  }
437 
438  # if appropriate query was found
439  if (isset($Query))
440  {
441  # if limited number of results were requested
442  if ($Limit !== NULL)
443  {
444  # add limit to query
445  $Query .= " LIMIT ".intval($Limit);
446  }
447 
448  # perform query and retrieve resource IDs
449  $this->DB->Query($Query);
450  $ResourceIds = $this->DB->FetchColumn("ResourceId");
451  }
452  }
453 
454  # return resource IDs to caller
455  return $ResourceIds;
456  }
457 
464  public function FilterNonViewableResources($ResourceIds, $User)
465  {
466  # compute this user's class
467  $UserClass = $this->ComputeUserClass($User);
468 
469  # generate an array where the keys are ResourceIds affected by
470  # user comparisons for the current user
471  $UserComparisonsRIDs = array_flip(
472  $this->ResourcesWhereUserComparisonsMatterForViewing($User));
473 
474  # (Note: We can use the $UserClass without a schema prefix as
475  # a cache key even though User Classes are schema specific
476  # because the values we're caching are ResourceIds. Since the
477  # ResourceIds already imply a schema, there's no ambiguity
478  # regarding which schema was involved when the stored UserClass
479  # was computed.)
480  if (!isset(self::$UserClassPermissionsCache[$UserClass]))
481  {
482  # grab all the ResourceIds for this user class
483  $this->DB->Query("SELECT ResourceId, CanView FROM UserPermsCache WHERE"
484  ." UserClass='".$UserClass."'");
485 
486  self::$UserClassPermissionsCache[$UserClass] = $this->DB->FetchColumn(
487  "CanView", "ResourceId");
488  }
489 
490  # filter out those not requested
491  $Cache = array_intersect_key(
492  self::$UserClassPermissionsCache[$UserClass],
493  array_flip($ResourceIds) );
494 
495  # figure out which resources we didn't have cached values for
496  # and iterate over those
497  $MissingIds = array_diff($ResourceIds, array_keys($Cache));
498 
499  $PerUserKey = $this->SchemaId.".UID_".$User->Id();
500 
501  # batch inserts up into not more than 1000 resources per query
502  $ChunkSize = 1000;
503  $QueryValues = array();
504  foreach ($MissingIds as $Id)
505  {
506  if (isset(self::$PerUserPermissionsCache[$PerUserKey]))
507  {
508  $CanView = self::$PerUserPermissionsCache[$PerUserKey];
509  }
510  else
511  {
512  # evaluate perms for this resource
513  if (Resource::ItemExists($Id))
514  {
515  $Resource = new Resource($Id);
516  $CanView = $Resource->UserCanView($User, FALSE);
517  }
518  else
519  {
520  $CanView = FALSE;
521  }
522 
523  # if this is a result we can cache persistently
524  # (i.e. not affected by user comparisons), do so
525  if (!isset($UserComparisonsRIDs[$Id]))
526  {
527  self::$UserClassPermissionsCache[$UserClass][$Id] = $CanView;
528 
529  # add this to our queue of inserts
530  $QueryValues[]= "(".$Id.",'".$UserClass."',".($CanView?"1":"0").")" ;
531 
532  # if this chunk is full, insert it into the db and clear our queue
533  if (count($QueryValues)>=$ChunkSize)
534  {
535  $this->DB->Query(
536  "INSERT INTO UserPermsCache (ResourceId, UserClass, CanView) "
537  ."VALUES ".implode(",", $QueryValues) );
538  $QueryValues = array();
539  }
540  }
541  else
542  {
543  # this isn't a result we should cache persistently
544  # in the database, but we still want to cache it
545  # within this page load
546  self::$PerUserPermissionsCache[$PerUserKey] = $CanView;
547  }
548 
549  }
550  $Cache[$Id] = $CanView;
551  }
552 
553  # if we have values left to insert, do so
554  if (count($QueryValues))
555  {
556  $this->DB->Query(
557  "INSERT INTO UserPermsCache (ResourceId, UserClass, CanView) "
558  ."VALUES ".implode(",", $QueryValues) );
559  }
560 
561  # if resource view permission check has any handlers that may
562  # modify our cached values
563  if ($GLOBALS["AF"]->IsHookedEvent("EVENT_RESOURCE_VIEW_PERMISSION_CHECK"))
564  {
565  # apply hooked functions to each value
566  foreach (array_keys($Cache) as $Id)
567  {
568  $SignalResult = $GLOBALS["AF"]->SignalEvent(
569  "EVENT_RESOURCE_VIEW_PERMISSION_CHECK",
570  array(
571  "Resource" => $Id,
572  "User" => $User,
573  "CanView" => $Cache[$Id],
574  "Schema" => $this->Schema, ));
575  $Cache[$Id] = $SignalResult["CanView"];
576  }
577  }
578 
579  # filter out the non-viewable resources, preserving the order
580  # of resources
581  return array_intersect($ResourceIds,
582  array_keys(array_filter($Cache)) );
583  }
584 
590  public function GetPossibleFieldNames()
591  {
592  # retrieve field names from schema
593  $FieldNames = array();
594  $Fields = $this->Schema->GetFields();
595  foreach ($Fields as $Field)
596  {
597  $FieldNames[$Field->Id()] = $Field->Name();
598  }
599 
600  # return field names to caller
601  return $FieldNames;
602  }
603 
617  public function GetMatchingResources(
618  $ValuesToMatch, $AllRequired=TRUE, $ReturnObjects=TRUE,
619  $Operator = "==")
620  {
621  # start out assuming we won't find any resources
622  $Resources = array();
623 
624  # fix up equality operator
625  if ($Operator == "==")
626  {
627  $Operator = "=";
628  }
629 
630  $LinkingTerm = "";
631  $Condition = "";
632 
633  # for each value
634  $Fields = $this->Schema->GetFields();
635  foreach ($ValuesToMatch as $FieldId => $Value)
636  {
637  # only equality supported for NULL
638  if ($Operator != "=" && $Value == "NULL")
639  {
640  throw new Exception(
641  "Invalid operator, ".$Operator." not supported for NULL");
642  }
643 
644  # convert supplied FieldId to canonical identifier
646  $FieldId, $this->SchemaId);
647 
648  # check that provided operator is sane
649  switch ($Fields[$FieldId]->Type())
650  {
656  $ValidOps = array("=");
657  break;
658 
660  $ValidOps = array("=", "!=");
661  break;
662 
666  $ValidOps = array("=", "!=", "<", "<=", ">", ">=");
667  break;
668 
669  default:
670  $ValidOps = array();
671  }
672 
673  if (!in_array($Operator, $ValidOps))
674  {
675  throw new Exception("Operator ".$Operator." not supported for "
676  .$Fields[$FieldId]->TypeAsName()." fields");
677  }
678 
679  # add SQL fragments to Condition as needed
680  switch ($Fields[$FieldId]->Type())
681  {
689  $DBFname = $Fields[$FieldId]->DBFieldName();
690  # add comparison to condition
691  if ($Value == "NULL")
692  {
693  $Condition .= $LinkingTerm."("
694  .$DBFname." IS NULL OR ".$DBFname." = '')";
695  }
696  else
697  {
698  $Condition .= $LinkingTerm.$DBFname." "
699  .$Operator." '".addslashes($Value)."'";
700  }
701  break;
702 
704  $DBFname = $Fields[$FieldId]->DBFieldName();
705 
706  if ($Value == "NULL")
707  {
708  $Condition .= $LinkingTerm."("
709  .$DBFname."X IS NULL AND "
710  .$DBFname."Y IS NULL)";
711  }
712  else
713  {
714  $Vx = addslashes($Value["X"]);
715  $Vy = addslashes($Value["Y"]);
716 
717  $Condition .= $LinkingTerm."("
718  .$DBFname."X = '".$Vx."' AND "
719  .$DBFname."Y = '".$Vy."')";
720  }
721  break;
722 
724  $TgtValues = array();
725  if (is_object($Value))
726  {
727  $TgtValues[]= $Value->Id();
728  }
729  elseif (is_numeric($Value))
730  {
731  $TgtValues[]= $Value;
732  }
733  elseif (is_array($Value))
734  {
735  foreach ($Value as $UserId => $UserNameOrObject)
736  {
737  $TgtValues[]= $UserId;
738  }
739  }
740 
741  # if no users were specified
742  if (!count($TgtValues))
743  {
744  # return no results (nothing matches nothing)
745  return array();
746  }
747  else
748  {
749  # add conditional to match specified users
750  $Condition .= $LinkingTerm."("
751  ."ResourceId IN (SELECT ResourceId FROM "
752  ."ResourceUserInts WHERE FieldId=".intval($FieldId)
753  ." AND UserId IN ("
754  .implode(",", $TgtValues).")) )";
755  }
756  break;
757 
758  default:
759  throw new Exception("Unsupported field type");
760  }
761 
762  $LinkingTerm = $AllRequired ? " AND " : " OR ";
763  }
764 
765  # if there were valid conditions
766  if (strlen($Condition))
767  {
768  # build query statment
769  $Query = "SELECT ResourceId FROM Resources WHERE (".$Condition
770  .") AND SchemaId = ".intval($this->SchemaId);
771 
772  # execute query to retrieve matching resource IDs
773  $this->DB->Query($Query);
774  $ResourceIds = $this->DB->FetchColumn("ResourceId");
775 
776  if ($ReturnObjects)
777  {
778  # retrieve resource objects
779  foreach ($ResourceIds as $Id)
780  {
781  $Resources[$Id] = new Resource($Id);
782  }
783  }
784  else
785  {
786  $Resources = $ResourceIds;
787  }
788  }
789 
790  # return any resources found to caller
791  return $Resources;
792  }
793 
806  $ValueId, $User, $ForegroundUpdate=FALSE)
807  {
808  # if the specified user is matched by any UserIs or UserIsNot
809  # privset conditions for any resources, then put them in a class
810  # by themselves
811  $UserClass = count($this->ResourcesWhereUserComparisonsMatterForViewing($User))
812  ? "UID_".$User->Id() :
813  $this->ComputeUserClass($User);
814 
815  $CacheKey = $this->SchemaId.".".$UserClass;
816  # if we haven't loaded any cached values, do so now
817  if (!isset(self::$VisibleResourceCountCache[$CacheKey]))
818  {
819  $this->DB->Query(
820  "SELECT ResourceCount, ValueId FROM "
821  ."VisibleResourceCounts WHERE "
822  ."SchemaId=".intval($this->SchemaId)
823  ." AND UserClass='".addslashes($UserClass)."'");
824 
825  self::$VisibleResourceCountCache[$CacheKey] = $this->DB->FetchColumn(
826  "ResourceCount", "ValueId");
827  }
828 
829  # if we don't have a cached value for this class
830  if (!isset(self::$VisibleResourceCountCache[$CacheKey][$ValueId]))
831  {
832  # if we're doing a foreground update
833  if ($ForegroundUpdate)
834  {
835  # run the update callback
837  $ValueId, $User->Id());
838 
839  # and call ourselves again
840  return $this->AssociatedVisibleResourceCount(
841  $ValueId, $User);
842  }
843  else
844  {
845  # otherwise (for background update), queue the update
846  # callback and return -1
847  $GLOBALS["AF"]->QueueUniqueTask(
848  array($this, "UpdateAssociatedVisibleResourceCount"),
849  array($ValueId, $User->Id() ) );
850  return -1;
851  }
852  }
853 
854  # owtherwise, return the cached data
855  return self::$VisibleResourceCountCache[$CacheKey][$ValueId];
856  }
857 
865  $ValueId, $UserId)
866  {
867  $User = new CWUser($UserId);
868 
869  # if the specified user is matched by any UserIs or UserIsNot
870  # privset conditions for any resources, then put them in a class
871  # by themselves
872  $UserClass = count($this->ResourcesWhereUserComparisonsMatterForViewing($User))
873  ? "UID_".$User->Id() :
874  $this->ComputeUserClass($User);
875 
876  $this->DB->Query(
877  "SELECT ResourceId FROM ResourceNameInts "
878  ."WHERE ControlledNameId=".intval($ValueId) );
879  $ResourceIds = $this->DB->FetchColumn("ResourceId");
880 
881  $ResourceIds = $this->FilterNonViewableResources(
882  $ResourceIds, $User);
883 
884  $ResourceCount = count($ResourceIds);
885 
886  $this->DB->Query(
887  "INSERT INTO VisibleResourceCounts "
888  ."(SchemaId, UserClass, ValueId, ResourceCount) "
889  ."VALUES ("
890  .intval($this->SchemaId).","
891  ."'".addslashes($UserClass)."',"
892  .intval($ValueId).","
893  .$ResourceCount.")");
894  }
895 
901  public function GetVisibleResourceCount(CWUser $User)
902  {
903  $ResourceIds = $this->DB->Query(
904  "SELECT ResourceId FROM Resources "
905  ."WHERE ResourceId > 0 AND SchemaId = ".intval($this->SchemaId));
906  $ResourceIds = $this->DB->FetchColumn("ResourceId");
907 
908  $ResourceIds = $this->FilterNonViewableResources(
909  $ResourceIds, $User);
910 
911  return count($ResourceIds);
912  }
913 
918  public function ClearVisibleResourceCountForValues($ValueIds)
919  {
920  # and clear our visible resource count cache
921  $this->DB->Query(
922  "DELETE FROM VisibleResourceCounts WHERE "
923  ."SchemaId=".intval($this->SchemaId)." AND "
924  ."ValueId IN (".implode(",", $ValueIds).")");
925  }
926 
932  public function ClearVisibleResourceCount($Resource)
933  {
934  # get all the CName and Option fields
935  $Fields = $this->Schema()->GetFields(
938 
939  # pull out the Values associated with those
940  $Values = [];
941  foreach ($Fields as $Field)
942  {
943  $Values += $Resource->Get($Field);
944  }
945 
946  # and clear our visible resource count cache
948  array_keys($Values));
949  }
950 
955  public function GetReleasedResourceTotal()
956  {
957  return $this->GetVisibleResourceCount(
959  }
960 
968  public static function FlattenMultiSchemaResourceList($ResourcesPerSchema)
969  {
970  $Result = [];
971  foreach ($ResourcesPerSchema as $SchemaId => $ResourceIds)
972  {
973  $Result = array_merge($Result, $ResourceIds);
974  }
975 
976  return $Result;
977  }
978 
987  public static function BuildMultiSchemaResourceList($ResourceIds)
988  {
989  $DB = new Database();
990  $DB->Query("SELECT ResourceId, SchemaId FROM Resources");
991  $ResourceSchemas = $DB->FetchColumn("SchemaId", "ResourceId");
992 
993  $Result = [];
994  foreach ($ResourceIds as $ResourceId)
995  {
996  $SchemaId = $ResourceSchemas[$ResourceId];
997  $Result[$SchemaId][]= $ResourceId;
998  }
999 
1000  return $Result;
1001  }
1002 
1008  public function GetResourceTotal()
1009  {
1010  return $this->DB->Query("
1011  SELECT COUNT(*) AS ResourceTotal
1012  FROM Resources
1013  WHERE ResourceId > 0
1014  AND SchemaId = ".intval($this->SchemaId),
1015  "ResourceTotal");
1016  }
1017 
1021  public static function ClearViewingPermsCache()
1022  {
1023  $DB = new Database();
1024  $DB->Query("DELETE FROM UserPermsCache");
1025  }
1026 
1031  public function ClearCaches()
1032  {
1033  self::$VisibleResourceCountCache = array();
1034  self::$UserClassPermissionsCache = array();
1035  self::$PerUserPermissionsCache = array();
1036  self::$UserClassCache = array();
1037  self::$UserComparisonResourceCache = array();
1038  self::$UserComparisonFieldCache = array();
1039  }
1040 
1041  # ---- PRIVATE INTERFACE -------------------------------------------------
1042 
1043  protected $Schema;
1044  protected $SchemaId;
1045 
1046  # internal caches
1047  private static $VisibleResourceCountCache;
1048  private static $UserClassPermissionsCache;
1049  private static $PerUserPermissionsCache;
1050  private static $UserClassCache;
1051  private static $UserComparisonResourceCache;
1052  private static $UserComparisonFieldCache;
1053 
1061  private function ComputeUserClass($User)
1062  {
1063  # put the anonymous user into their own user class, otherwise
1064  # use the UserId for a key into the ClassCache
1065  $UserId = $User->IsAnonymous() ? "XX-ANON-XX" : $User->Id();
1066 
1067  $CacheKey = $this->SchemaId.".".$UserId;
1068 
1069  # check if we have a cached UserClass for this User
1070  if (!isset($this->UserClassCache[$CacheKey]))
1071  {
1072  # assemble a list of the privilege flags (PRIV_SYSADMIN,
1073  # etc) that are checked when evaluating the UserCanView for
1074  # all fields in this schema
1075  $RelevantPerms = array();
1076 
1077  foreach ($this->Schema->GetFields() as $Field)
1078  {
1079  $RelevantPerms = array_merge(
1080  $RelevantPerms,
1081  $Field->ViewingPrivileges()->PrivilegeFlagsChecked() );
1082  }
1083  $RelevantPerms = array_unique($RelevantPerms);
1084 
1085  # whittle the list of all privs checked down to just the
1086  # list of privs that users in this class have
1087  $PermsInvolved = array();
1088  foreach ($RelevantPerms as $Perm)
1089  {
1090  if ($User->HasPriv($Perm))
1091  {
1092  $PermsInvolved[]= $Perm;
1093  }
1094  }
1095 
1096  # generate a string by concatenating all the involved
1097  # permissions then hashing the result (hashing gives
1098  # a fixed-size string for storing in the database)
1099  self::$UserClassCache[$CacheKey] = md5(implode( "-", $PermsInvolved ));
1100  }
1101 
1102  return self::$UserClassCache[$CacheKey];
1103  }
1104 
1114  private function ResourcesWhereUserComparisonsMatterForViewing($User)
1115  {
1116  $ResourceIds = array();
1117 
1118  # if we're checking the anonymous user, presume that
1119  # nothing will match
1120  if ($User->IsAnonymous())
1121  {
1122  return $ResourceIds;
1123  }
1124 
1125  $CacheKey = $this->SchemaId.".".$User->Id();
1126  if (!isset(self::$UserComparisonResourceCache[$CacheKey]))
1127  {
1128  $Schema = new MetadataSchema($this->SchemaId);
1129 
1130  # for each comparison type
1131  foreach (array("==", "!=") as $ComparisonType)
1132  {
1133  $UserComparisonFields = $this->GetUserComparisonFields(
1134  $ComparisonType);
1135 
1136  # if we have any fields to check
1137  if (count($UserComparisonFields) > 0 )
1138  {
1139  # query the database for resources where one or more of the
1140  # user comparisons will be satisfied
1141  $SqlOp = ($ComparisonType == "==") ? "= " : "!= ";
1142  $DB = new Database();
1143  $DB->Query("SELECT R.ResourceId as ResourceId FROM ".
1144  "Resources R, ResourceUserInts RU WHERE ".
1145  "R.SchemaId = ".$this->SchemaId." AND ".
1146  "R.ResourceId = RU.ResourceId AND ".
1147  "RU.UserId ".$SqlOp.$User->Id()." AND ".
1148  "RU.FieldId IN (".implode(",", $UserComparisonFields).")");
1149  $Result = $DB->FetchColumn("ResourceId");
1150 
1151  # merge those resources into our results
1152  $ResourceIds = array_merge(
1153  $ResourceIds,
1154  $Result);
1155  }
1156  }
1157 
1158  self::$UserComparisonResourceCache[$CacheKey] = array_unique($ResourceIds);
1159  }
1160 
1161  return self::$UserComparisonResourceCache[$CacheKey];
1162  }
1163 
1164 
1170  private function GetUserComparisonFields($ComparisonType)
1171  {
1172  $CacheKey = $this->SchemaId.".".$ComparisonType;
1173  if (!isset(self::$UserComparisonFieldCache[$CacheKey]))
1174  {
1175  # iterate through all the fields in the schema,
1176  # constructing a list of the User fields implicated
1177  # in comparisons of the desired type
1178  $UserComparisonFields = array();
1179  foreach ($this->Schema->GetFields() as $Field)
1180  {
1181  $UserComparisonFields = array_merge(
1182  $UserComparisonFields,
1183  $Field->ViewingPrivileges()->FieldsWithUserComparisons(
1184  $ComparisonType) );
1185  }
1186  self::$UserComparisonFieldCache[$CacheKey] =
1187  array_unique($UserComparisonFields);
1188  }
1189 
1190  return self::$UserComparisonFieldCache[$CacheKey];
1191  }
1192 }
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:948
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...
static FlattenMultiSchemaResourceList($ResourcesPerSchema)
Take an array keyed by SchemaId with elements giving arrays of ResourceIds and merge it to a flattene...
GetRatedResourceUserCount()
Return number of users who have rated resources.
Metadata schema (in effect a Factory class for MetadataField).
static Create($Term, $FieldId)
Create a new empty ControlledName if it&#39;s not already present.
SQL database abstraction object with smart query caching.
Definition: Database.php:22
static Create($Name, $FieldId, $ParentId=NULL)
Add new classification to the hierarchy.
static BuildMultiSchemaResourceList($ResourceIds)
Take an array of ResourceIds and split it into an array keyed by SchemaId where the elements are arra...
GetResourceIdsSortedBy($FieldId, $Ascending=TRUE, $Limit=NULL)
Get resource IDs sorted by specified field.
static GetCanonicalFieldIdentifier($Field, $SchemaId=NULL)
Retrieve canonical identifier for field.
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.
static ClearViewingPermsCache()
Clear the cache of viewable resources.
const MDFTYPE_CONTROLLEDNAME
ClearVisibleResourceCountForValues($ValueIds)
Clear cache of visible resources associated with a 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.
ClearVisibleResourceCount($Resource)
Clear database visibility caches for all the CNames referenced by a specified resource.
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.