CWIS Developer Documentation
MetadataSchema.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: MetadataSchema.php
4 #
5 # Part of the Collection Workflow Integration System (CWIS)
6 # Copyright 2012-2013 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu/cwis
8 #
9 
14 {
15 
16  # ---- PUBLIC INTERFACE --------------------------------------------------
17 
18  # metadata field base types
19  # (must parallel MetadataFields.FieldType declaration in install/CreateTables.sql
20  # and MetadataField::$FieldTypeDBEnums declaration below)
21  const MDFTYPE_TEXT = 1;
22  const MDFTYPE_PARAGRAPH = 2;
23  const MDFTYPE_NUMBER = 4;
24  const MDFTYPE_DATE = 8;
25  const MDFTYPE_TIMESTAMP = 16;
26  const MDFTYPE_FLAG = 32;
27  const MDFTYPE_TREE = 64;
29  const MDFTYPE_OPTION = 256;
30  const MDFTYPE_USER = 512;
31  const MDFTYPE_IMAGE = 1024;
32  const MDFTYPE_FILE = 2048;
33  const MDFTYPE_URL = 4096;
34  const MDFTYPE_POINT = 8192;
35  const MDFTYPE_REFERENCE = 16384;
36 
37  # types of field ordering
38  const MDFORDER_DISPLAY = 1;
39  const MDFORDER_EDITING = 2;
41 
42  # error status codes
43  const MDFSTAT_OK = 1;
44  const MDFSTAT_ERROR = 2;
48  const MDFSTAT_ILLEGALNAME = 32;
50  const MDFSTAT_ILLEGALLABEL = 128;
51 
52  # special schema IDs
53  const SCHEMAID_DEFAULT = 0;
54  const SCHEMAID_RESOURCES = 0;
55  const SCHEMAID_USER = 1;
56  const SCHEMAID_USERS = 1;
57 
58  # resource names
59  const RESOURCENAME_DEFAULT = "Resource";
60  const RESOURCENAME_USER = "User";
61 
62  # names used for display and edit orders
63  const ORDER_DISPLAY_NAME = "Display";
64  const ORDER_EDIT_NAME = "Edit";
65 
66  # maximum option list size for GetFieldsAsOptionList
67  const MAX_OPT_LIST_SIZE = 20;
68 
80  public function __construct($SchemaId = self::SCHEMAID_DEFAULT)
81  {
82  # set up item factory base class
83  parent::__construct("MetadataField", "MetadataFields",
84  "FieldId", "FieldName", FALSE,
85  "SchemaId = ".intval($SchemaId));
86 
87  # make sure schema info cache is loaded
88  if (self::$ValueCache === NULL)
89  {
90  $this->DB->Query("SELECT * FROM MetadataSchemas");
91  self::$ValueCache = array();
92  foreach ($this->DB->FetchRows() as $Row)
93  {
94  self::$ValueCache[$Row["SchemaId"]] = $Row;
95  }
96  }
97 
98  # if standard field mappings have not yet been loaded
99  if (!isset(self::$FieldMappings))
100  {
101  # load metadata field IDs to check against
102  $this->DB->Query("SELECT SchemaId, FieldId"
103  ." FROM MetadataFields");
104  $FieldSchemaIds = $this->DB->FetchColumn("SchemaId", "FieldId");
105 
106  # for each standard field mapping
107  $this->DB->Query("SELECT * FROM StandardMetadataFieldMappings");
108  foreach ($this->DB->FetchRows() as $Row)
109  {
110  # if mapping is for a valid field in appropriate schema
111  if (isset($FieldSchemaIds[$Row["FieldId"]])
112  && ($FieldSchemaIds[$Row["FieldId"]] == $Row["SchemaId"]))
113  {
114  # save mapping
115  self::$FieldMappings[$Row["SchemaId"]][$Row["Name"]] =
116  $Row["FieldId"];
117  }
118  else
119  {
120  # error out
121  throw new Exception("Standard field mapping for"
122  ." \"".$Row["Name"]."\" found with"
123  ." invalid schema/field ID combination"
124  ." (".$Row["SchemaId"]."/".$Row["FieldId"].").");
125  }
126  }
127  }
128 
129  # make sure specified schema ID is valid
130  if (!isset(self::$ValueCache[$SchemaId]))
131  {
132  throw new InvalidArgumentException("Attempt to load metadata schema"
133  ." with invalid ID (".$SchemaId.") at "
134  .StdLib::GetMyCaller().".");
135  }
136 
137  # load schema info from cache
138  $Info = self::$ValueCache[$SchemaId];
139  $this->Id = $SchemaId;
140  $this->AuthoringPrivileges = new PrivilegeSet($Info["AuthoringPrivileges"]);
141  $this->EditingPrivileges = new PrivilegeSet($Info["EditingPrivileges"]);
142  $this->ViewingPrivileges = new PrivilegeSet($Info["ViewingPrivileges"]);
143  $this->ViewPage = $Info["ViewPage"];
144  if (!isset(self::$FieldMappings[$this->Id]))
145  {
146  self::$FieldMappings[$this->Id] = array();
147  }
148  }
149 
159  public static function GetConstantName($Value, $Prefix = NULL)
160  {
161  # retrieve all constants for class
162  $Reflect = new ReflectionClass(get_class());
163  $Constants = $Reflect->getConstants();
164 
165  # for each constant
166  foreach ($Constants as $CName => $CValue)
167  {
168  # if value matches and prefix (if supplied) matches
169  if (($CValue == $Value)
170  && (($Prefix === NULL) || (strpos($CName, $Prefix) === 0)))
171  {
172  # return name to caller
173  return $CName;
174  }
175  }
176 
177  # report to caller that no matching constant was found
178  return NULL;
179  }
180 
198  public static function Create($Name,
199  PrivilegeSet $AuthorPrivs = NULL,
200  PrivilegeSet $EditPrivs = NULL,
201  PrivilegeSet $ViewPrivs = NULL,
202  $ViewPage = "",
203  $ResourceName = NULL)
204  {
205  # supply privilege settings if none provided
206  if ($AuthorPrivs === NULL) { $AuthorPrivs = new PrivilegeSet(); }
207  if ($EditPrivs === NULL) { $EditPrivs = new PrivilegeSet(); }
208  if ($ViewPrivs === NULL) { $ViewPrivs = new PrivilegeSet(); }
209 
210  # add schema to database
211  $DB = new Database;
212  if (strtoupper($Name) == "RESOURCES")
213  {
214  $Id = self::SCHEMAID_DEFAULT;
215  }
216  elseif (strtoupper($Name) == "USER")
217  {
218  $Id = self::SCHEMAID_USER;
219  }
220  else
221  {
222  $Id = $DB->Query("SELECT SchemaId FROM MetadataSchemas"
223  ." ORDER BY SchemaId DESC LIMIT 1", "SchemaId") + 1;
224  }
225  $DB->Query("INSERT INTO MetadataSchemas"
226  ." (SchemaId, Name, ViewPage,"
227  ." AuthoringPrivileges, EditingPrivileges, ViewingPrivileges)"
228  ." VALUES (".intval($Id).","
229  ."'".addslashes($Name)."',"
230  ."'".$DB->EscapeString($ViewPage)."',"
231  ."'".$DB->EscapeString($AuthorPrivs->Data())."',"
232  ."'".$DB->EscapeString($EditPrivs->Data())."',"
233  ."'".$DB->EscapeString($ViewPrivs->Data())."')");
234 
235  # clear schema data cache so it will be reloaded
236  self::$ValueCache = NULL;
237 
238  # construct the new schema
239  $Schema = new MetadataSchema($Id);
240 
241  # set schema name if none supplied
242  if (!strlen($Name))
243  {
244  $Schema->Name("Metadata Schema ".$Id);
245  }
246 
247  # set the resource name if one is supplied
248  if ($ResourceName === NULL)
249  {
250  $ResourceName = StdLib::Singularize($Name);
251  }
252  $Schema->ResourceName($ResourceName);
253 
254  # create display and edit orders
255  MetadataFieldOrder::Create($Schema, self::ORDER_DISPLAY_NAME, array() );
256  MetadataFieldOrder::Create($Schema, self::ORDER_EDIT_NAME, array() );
257 
258  # return the new schema
259  return $Schema;
260  }
261 
266  public function Delete()
267  {
268  # delete resources associated with schema
269  $RFactory = new ResourceFactory($this->Id);
270  $ResourceIds = $RFactory->GetItemIds();
271  foreach ($ResourceIds as $ResourceId)
272  {
273  $Resource = new Resource($ResourceId);
274  $Resource->Delete();
275  }
276 
277  # unmap all the mapped fields
278  $MappedNames = array_keys(self::$FieldMappings[$this->Id]);
279  foreach ($MappedNames as $MappedName)
280  {
281  $this->StdNameToFieldMapping($MappedName, NULL);
282  }
283 
284  # delete fields associated with schema
285  $Fields = $this->GetFields(NULL, NULL, TRUE, TRUE);
286  foreach ($Fields as $FieldId => $Field)
287  {
288  $this->DropField($FieldId);
289  }
290 
291  # delete metadata field orders associated with schema
292  foreach (MetadataFieldOrder::GetOrdersForSchema($this) as $Order)
293  {
294  $Order->Delete();
295  }
296 
297  # remove schema info from database
298  $this->DB->Query("DELETE FROM MetadataSchemas WHERE SchemaId = "
299  .intval($this->Id));
300  }
301 
307  public static function SchemaExistsWithId($SchemaId)
308  {
309  if (!is_numeric($SchemaId))
310  {
311  return FALSE;
312  }
313  $DB = new Database();
314  $DB->Query("SELECT * FROM MetadataSchemas"
315  ." WHERE SchemaId = ".intval($SchemaId));
316  return ($DB->NumRowsSelected() > 0) ? TRUE : FALSE;
317  }
318 
324  public function Id()
325  {
326  # return value to caller
327  return intval($this->Id);
328  }
329 
335  public function Name($NewValue = DB_NOVALUE)
336  {
337  return $this->UpdateValue("Name", $NewValue);
338  }
339 
347  public function AbbreviatedName($NewValue = DB_NOVALUE)
348  {
349  $AName = $this->UpdateValue("AbbreviatedName", $NewValue);
350  if (!strlen($AName))
351  {
352  $AName = strtoupper(substr($this->Name(), 0, 1));
353  }
354  return $AName;
355  }
356 
362  public function ResourceName($NewValue = DB_NOVALUE)
363  {
364  $RName = $this->UpdateValue("ResourceName", $NewValue);
365  if (!strlen($RName))
366  {
367  $RName = self::RESOURCENAME_DEFAULT;
368  }
369  return $RName;
370  }
371 
377  public function ViewPage($NewValue = DB_NOVALUE)
378  {
379  return $this->UpdateValue("ViewPage", $NewValue);
380  }
381 
387  public function AuthoringPrivileges(PrivilegeSet $NewValue = NULL)
388  {
389  # if new privileges supplied
390  if ($NewValue !== NULL)
391  {
392  # store new privileges in database
393  $this->UpdateValue("AuthoringPrivileges", $NewValue->Data());
394  $this->AuthoringPrivileges = $NewValue;
395  }
396 
397  # return current value to caller
398  return $this->AuthoringPrivileges;
399  }
400 
406  public function EditingPrivileges(PrivilegeSet $NewValue = NULL)
407  {
408  # if new privileges supplied
409  if ($NewValue !== NULL)
410  {
411  # store new privileges in database
412  $this->UpdateValue("EditingPrivileges", $NewValue->Data());
413  $this->EditingPrivileges = $NewValue;
414  }
415 
416  # return current value to caller
417  return $this->EditingPrivileges;
418  }
419 
425  public function ViewingPrivileges(PrivilegeSet $NewValue = NULL)
426  {
427  # if new privileges supplied
428  if ($NewValue !== NULL)
429  {
430  # store new privileges in database
431  $this->UpdateValue("ViewingPrivileges", $NewValue->Data());
432  $this->ViewingPrivileges = $NewValue;
433  }
434 
435  # return current value to caller
436  return $this->ViewingPrivileges;
437  }
438 
446  public function UserCanAuthor($User)
447  {
448  # get authoring privilege set for schema
449  $AuthorPrivs = $this->AuthoringPrivileges();
450 
451  # user can author if privileges are greater than resource set
452  $CanAuthor = $AuthorPrivs->MeetsRequirements($User);
453 
454  # allow plugins to modify result of permission check
455  $SignalResult = $GLOBALS["AF"]->SignalEvent(
456  "EVENT_RESOURCE_AUTHOR_PERMISSION_CHECK", array(
457  "Schema" => $this,
458  "User" => $User,
459  "CanAuthor" => $CanAuthor));
460  $CanAuthor = $SignalResult["CanAuthor"];
461 
462  # report back to caller whether user can author field
463  return $CanAuthor;
464  }
465 
473  public function UserCanEdit($User)
474  {
475  # get editing privilege set for schema
476  $EditPrivs = $this->EditingPrivileges();
477 
478  # user can edit if privileges are greater than resource set
479  $CanEdit = $EditPrivs->MeetsRequirements($User);
480 
481  # allow plugins to modify result of permission check
482  $SignalResult = $GLOBALS["AF"]->SignalEvent(
483  "EVENT_RESOURCE_EDIT_PERMISSION_CHECK", array(
484  "Resource" => NULL,
485  "User" => $User,
486  "CanEdit" => $CanEdit,
487  "Schema" => $this, ));
488  $CanEdit = $SignalResult["CanEdit"];
489 
490  # report back to caller whether user can edit field
491  return $CanEdit;
492  }
493 
501  public function UserCanView($User)
502  {
503  # get viewing privilege set for schema
504  $ViewPrivs = $this->ViewingPrivileges();
505 
506  # user can view if privileges are greater than resource set
507  $CanView = $ViewPrivs->MeetsRequirements($User);
508 
509  # allow plugins to modify result of permission check
510  $SignalResult = $GLOBALS["AF"]->SignalEvent(
511  "EVENT_RESOURCE_VIEW_PERMISSION_CHECK", array(
512  "Resource" => NULL,
513  "User" => $User,
514  "CanView" => $CanView,
515  "Schema" => $this, ));
516  $CanView = $SignalResult["CanView"];
517 
518  # report back to caller whether user can view field
519  return $CanView;
520  }
521 
527  public function GetViewPageIdParameter()
528  {
529  # get the query/GET parameters for the view page
530  $Query = parse_url($this->ViewPage(), PHP_URL_QUERY);
531 
532  # the URL couldn't be parsed
533  if (!is_string($Query))
534  {
535  return NULL;
536  }
537 
538  # parse the GET parameters out of the query string
539  $GetVars = ParseQueryString($Query);
540 
541  # search for the ID parameter
542  $Result = array_search("\$ID", $GetVars);
543 
544  return $Result !== FALSE ? $Result : NULL;
545  }
546 
559  public function PathMatchesViewPage($Path)
560  {
561  # get the query/GET parameters for the view page
562  $Query = parse_url($this->ViewPage(), PHP_URL_QUERY);
563 
564  # can't perform matching if the URL couldn't be parsed
565  if (!is_string($Query))
566  {
567  return FALSE;
568  }
569 
570  # parse the GET parameters out of the query string
571  $GetVars = ParseQueryString($Query);
572 
573  # now, get the query/GET parameters from the path given
574  $PathQuery = parse_url($Path, PHP_URL_QUERY);
575 
576  # can't perform matching if the URL couldn't be parsed
577  if (!is_string($PathQuery))
578  {
579  return FALSE;
580  }
581 
582  # parse the GET parameters out of the path's query string
583  $PathGetVars = ParseQueryString($PathQuery);
584 
585  # make sure the given path GET parameters contain at least the GET
586  # parameters from the view page and that all non-variable parameters are
587  # equal. the path GET parameters may contain more, which is okay
588  foreach ($GetVars as $GetVarName => $GetVarValue)
589  {
590  # there's a required parameter that is not included in the path GET
591  # parameters
592  if (!array_key_exists($GetVarName, $PathGetVars))
593  {
594  return FALSE;
595  }
596 
597  # require the path's value to be equal to the view page's value if
598  # the view page's value is not a variable,
599  if ($PathGetVars[$GetVarName] != $GetVarValue
600  && (!strlen($GetVarValue) || $GetVarValue{0} != "$"))
601  {
602  return FALSE;
603  }
604  }
605 
606  # the path matches the view page path
607  return TRUE;
608  }
609 
619  public function AddField(
620  $FieldName, $FieldType, $Optional = TRUE, $DefaultValue = NULL)
621  {
622  # clear any existing error messages
623  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
624  { unset($this->ErrorMsgs[__METHOD__]); }
625 
626  # create new field
627  try
628  {
629  $Field = MetadataField::Create($this->Id(), $FieldType,
630  $FieldName, $Optional, $DefaultValue);
631  }
632  catch (Exception $Exception)
633  {
634  $this->ErrorMsgs[__METHOD__][] = $Exception->getMessage();
635  $Field = NULL;
636  }
637 
638  # clear internal caches to make sure new field is recognized going forward
639  $this->ClearCaches();
640 
641  # return new field to caller
642  return $Field;
643  }
644 
659  public function AddFieldsFromXmlFile($FileName, $Owner = NULL, $TestRun = FALSE)
660  {
661  # clear loading status
662  $this->NewFields = array();
663  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
664  { unset($this->ErrorMsgs[__METHOD__]); }
665 
666  # check that file exists and is readable
667  if (!file_exists($FileName))
668  {
669  $this->ErrorMsgs[__METHOD__][] = "Could not find XML file '"
670  .$FileName."'.";
671  return FALSE;
672  }
673  elseif (!is_readable($FileName))
674  {
675  $this->ErrorMsgs[__METHOD__][] = "Could not read from XML file '"
676  .$FileName."'.";
677  return FALSE;
678  }
679 
680  # load XML from file
681  libxml_use_internal_errors(TRUE);
682  $XmlData = simplexml_load_file($FileName);
683  $Errors = libxml_get_errors();
684  libxml_use_internal_errors(FALSE);
685 
686  # if XML load failed
687  if ($XmlData === FALSE)
688  {
689  # retrieve XML error messages
690  foreach ($Errors as $Err)
691  {
692  $ErrType = ($Err->level == LIBXML_ERR_WARNING) ? "Warning"
693  : (($Err->level == LIBXML_ERR_WARNING) ? "Error"
694  : "Fatal Error");
695  $this->ErrorMsgs[__METHOD__][] = "XML ".$ErrType.": ".$Err->message
696  ." (".$Err->file.":".$Err->line.",".$Err->column.")";
697  }
698  }
699  # else if no metadata fields found record error message
700  elseif (!count($XmlData->MetadataField))
701  {
702  $this->ErrorMsgs[__METHOD__][] = "No metadata fields found.";
703  }
704  # else process metadata fields
705  else
706  {
707  # for each metadata field entry found
708  $FieldsAdded = 0;
709  $FieldIndex = 0;
710  foreach ($XmlData->MetadataField as $FieldXml)
711  {
712  $FieldIndex++;
713 
714  # pull out field type if present
715  if (isset($FieldXml->Type))
716  {
717  $FieldType = "MetadataSchema::".$FieldXml->Type;
718  if (!defined($FieldType))
719  {
720  $FieldType = "MetadataSchema::MDFTYPE_"
721  .strtoupper(preg_replace("/\\s+/", "",
722  $FieldXml->Type));
723  }
724  }
725 
726  # if required values are missing
727  if (!isset($FieldXml->Name)
728  || !isset($FieldXml->Type)
729  || !isset($FieldType)
730  || !defined($FieldType))
731  {
732  # add error message about required value missing
733  if (!isset($FieldXml->Name))
734  {
735  $this->ErrorMsgs[__METHOD__][] =
736  "Field name not found (MetadataField #"
737  .$FieldIndex.").";
738  }
739  if (!isset($FieldXml->Type)
740  || !isset($FieldType)
741  || !defined($FieldType))
742  {
743  $this->ErrorMsgs[__METHOD__][] =
744  "Valid type not found for field '"
745  .$FieldXml->Name."' (MetadataField #"
746  .$FieldIndex.").";
747  }
748  }
749  # else if there is not already a field with this name
750  elseif (!$this->NameIsInUse(trim($FieldXml->Name)))
751  {
752  # create new field
753  $Field = $this->AddField($FieldXml->Name, constant($FieldType));
754 
755  # if field creation failed
756  if ($Field === NULL)
757  {
758  # add any error message to our error list
759  $ErrorMsgs = $this->ErrorMessages("AddField");
760  foreach ($ErrorMsgs as $Msg)
761  {
762  $this->ErrorMsgs[__METHOD__][] =
763  $Msg." (AddField)";
764  }
765  }
766  else
767  {
768  # add field to list of created fields
769  $this->NewFields[$Field->Id()] = $Field;
770 
771  # assume no vocabulary to load
772  $VocabToLoad = NULL;
773 
774  # for other field attributes
775  foreach ($FieldXml as $MethodName => $Value)
776  {
777  # if tags look valid and have not already been set
778  if (method_exists($Field, $MethodName)
779  && ($MethodName != "Name")
780  && ($MethodName != "Type"))
781  {
782  # if tag indicates privilege set
783  if (preg_match("/^[a-z]+Privileges\$/i",
784  $MethodName))
785  {
786  # save element for later processing
787  $PrivilegesToSet[$Field->Id()][$MethodName] = $Value;
788  }
789  else
790  {
791  # condense down any extraneous whitespace
792  $Value = preg_replace("/\s+/", " ", trim($Value));
793 
794  # set value for field
795  $Field->$MethodName($Value);
796  }
797  }
798  elseif ($MethodName == "VocabularyFile")
799  {
800  $VocabToLoad = $Value;
801  }
802  }
803 
804  # save the temp ID so that any privileges to set
805  # can be mapped to the actual ID when the field is
806  # made permanent
807  $TempId = $Field->Id();
808 
809  # make new field permanent
810  $Field->IsTempItem(FALSE);
811 
812  # load any vocabularies
813  if ($VocabToLoad !== NULL)
814  {
815  $Field->LoadVocabulary($VocabToLoad);
816  }
817 
818  # map privileges to set to the permanent field ID
819  if (isset($PrivilegesToSet) &&
820  isset($PrivilegesToSet[$TempId]) )
821  {
822  # copy the privileges over
823  $PrivilegesToSet[$Field->Id()] =
824  $PrivilegesToSet[$TempId];
825 
826  # remove the values for the temp ID
827  unset($PrivilegesToSet[$TempId]);
828  }
829  }
830  }
831  }
832 
833  # if we have schema-level privileges to set
834  if (count($XmlData->SchemaPrivileges))
835  {
836  foreach ($XmlData->SchemaPrivileges->children() as $PrivName => $PrivXml)
837  {
838  # if our current value for this privset is empty,
839  # take the one from the file
840  if ($this->$PrivName()->ComparisonCount() == 0)
841  {
842  # extract the values to set from the XML
843  $Value = $this->ConvertXmlToPrivilegeSet($PrivXml);
844  # set the privilege
845  $this->$PrivName($Value);
846  }
847  }
848  }
849 
850  # if we have privileges to set
851  if (isset($PrivilegesToSet))
852  {
853  # for each field with privileges
854  foreach ($PrivilegesToSet as $FieldId => $Privileges)
855  {
856  # load the field for which to set the privileges
857  $Field = new MetadataField($FieldId);
858 
859  # for each set of privileges for field
860  foreach ($Privileges as $MethodName => $Value)
861  {
862  # convert privilege value
863  $Value = $this->ConvertXmlToPrivilegeSet($Value);
864 
865  # if conversion failed
866  if ($Value === NULL)
867  {
868  # add resulting error messages to our list
869  $ErrorMsgs = $this->ErrorMessages(
870  "ConvertXmlToPrivilegeSet");
871  foreach ($ErrorMsgs as $Msg)
872  {
873  $this->ErrorMsgs[__METHOD__][] =
874  $Msg." (ConvertXmlToPrivilegeSet)";
875  }
876  }
877  else
878  {
879  # set value for field
880  $Field->$MethodName($Value);
881  }
882  }
883  }
884  }
885 
886  # if errors were found during creation
887  if (array_key_exists(__METHOD__, $this->ErrorMsgs) || $TestRun)
888  {
889  # remove any fields that were created
890  foreach ($this->NewFields as $Field)
891  {
892  $Field->Drop();
893  }
894  $this->NewFields = array();
895  }
896  else
897  {
898  # set owner for new fields (if supplied)
899  if ($Owner !== NULL)
900  {
901  foreach ($this->NewFields as $Field)
902  {
903  $Field->Owner($Owner);
904  }
905  }
906 
907  # if there were standard field mappings included
908  if (isset($XmlData->StandardFieldMapping))
909  {
910  # for each standard field mapping found
911  foreach ($XmlData->StandardFieldMapping as $MappingXml)
912  {
913  # if required values are supplied
914  if (isset($MappingXml->Name)
915  && isset($MappingXml->StandardName))
916  {
917  # get ID for specified field
918  $FieldName = (string)$MappingXml->Name;
919  $StandardName = (string)$MappingXml->StandardName;
920  $FieldId = $this->GetFieldIdByName($FieldName);
921 
922  # if field ID was found
923  if ($FieldId !== FALSE)
924  {
925  # set standard field mapping
926  $this->StdNameToFieldMapping(
927  $StandardName, $FieldId);
928  }
929  else
930  {
931  # log error about field not found
932  $this->ErrorMsgs[__METHOD__][] =
933  "Field not found with name '".$FieldName
934  ."' to map to standard field name '"
935  .$StandardName."'.";
936  }
937  }
938  else
939  {
940  # log error about missing value
941  if (!isset($MappingXml->Name))
942  {
943  $this->ErrorMsgs[__METHOD__][] =
944  "Field name missing for standard"
945  ." field mapping.";
946  }
947  if (!isset($MappingXml->StandardName))
948  {
949  $this->ErrorMsgs[__METHOD__][] =
950  "Standard field name missing for"
951  ." standard field mapping.";
952  }
953  }
954  }
955  }
956  }
957  }
958 
959  # report success or failure based on whether errors were recorded
960  return (array_key_exists(__METHOD__, $this->ErrorMsgs)) ? FALSE : TRUE;
961  }
962 
968  public function NewFields()
969  {
970  return $this->NewFields;
971  }
972 
982  public function ErrorMessages($Method = NULL)
983  {
984  if ($Method === NULL)
985  {
986  return $this->ErrorMsgs;
987  }
988  else
989  {
990  if (!method_exists($this, $Method))
991  {
992  throw new Exception("Error messages requested for non-existent"
993  ." method (".$Method.").");
994  }
995  return array_key_exists(__CLASS__."::".$Method, $this->ErrorMsgs)
996  ? $this->ErrorMsgs[__CLASS__."::".$Method] : array();
997  }
998  }
999 
1009  public function AddFieldFromXml($Xml)
1010  {
1011  # assume field addition will fail
1012  $Field = self::MDFSTAT_ERROR;
1013 
1014  # add XML prefixes if needed
1015  $Xml = trim($Xml);
1016  if (!preg_match("/^<\?xml/i", $Xml))
1017  {
1018  if (!preg_match("/^<document>/i", $Xml))
1019  {
1020  $Xml = "<document>".$Xml."</document>";
1021  }
1022  $Xml = "<?xml version='1.0'?".">".$Xml;
1023  }
1024 
1025  # parse XML
1026  $XmlData = simplexml_load_string($Xml);
1027 
1028  # if required values are present
1029  if (is_object($XmlData)
1030  && isset($XmlData->Name)
1031  && isset($XmlData->Type)
1032  && constant("MetadataSchema::".$XmlData->Type))
1033  {
1034  # create the metadata field
1035  $Field = $this->AddField(
1036  $XmlData->Name,
1037  constant("MetadataSchema::".$XmlData->Type));
1038 
1039  # if field creation succeeded
1040  if ($Field != NULL)
1041  {
1042  # for other field attributes
1043  foreach ($XmlData as $MethodName => $Value)
1044  {
1045  # if they look valid and have not already been set
1046  if (method_exists($Field, $MethodName)
1047  && ($MethodName != "Name")
1048  && ($MethodName != "Type"))
1049  {
1050  # if tag indicates privilege set
1051  if (preg_match("/^[a-z]+Privileges\$/i",
1052  $MethodName))
1053  {
1054  # save element for later processing
1055  $PrivilegesToSet[$MethodName] = $Value;
1056  }
1057  else
1058  {
1059  # condense down any extraneous whitespace
1060  $Value = preg_replace("/\s+/", " ", trim($Value));
1061 
1062  # set value for field
1063  $Field->$MethodName($Value);
1064  }
1065  }
1066  }
1067 
1068  # make new field permanent
1069  $Field->IsTempItem(FALSE);
1070 
1071  # if we have privileges to set
1072  if (isset($PrivilegesToSet))
1073  {
1074  # for each set of privileges for field
1075  foreach ($PrivilegesToSet as $MethodName => $Value)
1076  {
1077  # convert privilege value
1078  $Value = $this->ConvertXmlToPrivilegeSet($Value);
1079 
1080  # if conversion failed
1081  if ($Value === NULL)
1082  {
1083  # add resulting error messages to our list
1084  $ErrorMsgs = $this->ErrorMessages(
1085  "ConvertXmlToPrivilegeSet");
1086  foreach ($ErrorMsgs as $Msg)
1087  {
1088  $this->ErrorMsgs[__METHOD__][] =
1089  $Msg." (ConvertXmlToPrivilegeSet)";
1090  }
1091  }
1092  else
1093  {
1094  # set value for field
1095  $Field->$MethodName($Value);
1096  }
1097  }
1098  }
1099  }
1100  }
1101 
1102  # return new field (if any) to caller
1103  return $Field;
1104  }
1105 
1111  public function DropField($FieldId)
1112  {
1113  $Field = $this->GetField($FieldId);
1114  if ($Field === NULL)
1115  {
1116  return FALSE;
1117  }
1118 
1119  # verify that this field is not mapped prior to dropping it
1120  foreach (self::$FieldMappings[$this->Id] as $Name => $FieldId)
1121  {
1122  if ($Field->Id() == $FieldId)
1123  {
1124  throw new Exception(
1125  "Attempt to delete ".$Field->Name()
1126  .", which is mapped as the standard ".$Name
1127  ." in the ".$this->Name()." Schema.");
1128  }
1129  }
1130 
1131  $GLOBALS["AF"]->SignalEvent("EVENT_PRE_FIELD_DELETE",
1132  array("FieldId" => $Field->Id()) );
1133 
1134  $Field->Drop();
1135 
1136  return TRUE;
1137  }
1138 
1145  public function GetField($FieldId)
1146  {
1147  # convert field name to ID if necessary
1148  if (!is_numeric($FieldId))
1149  {
1150  $FieldName = $FieldId;
1151  $FieldId = $this->GetFieldIdByName($FieldName);
1152  if ($FieldId === FALSE)
1153  {
1154  throw new InvalidArgumentException("Attempt to retrieve field"
1155  ." with unknown name (".$FieldName.").");
1156  }
1157  }
1158 
1159  # if caching is off or field is not already loaded
1160  if (!isset(self::$FieldCache[$FieldId]))
1161  {
1162  self::$FieldCache[$FieldId] = new MetadataField($FieldId);
1163  }
1164 
1165  # if field was from a different schema, bail
1166  if (self::$FieldCache[$FieldId]->SchemaId() != $this->Id())
1167  {
1168  throw new InvalidArgumentException(
1169  "Attempt to retrieve a field from a different schema");
1170  }
1171 
1172  return self::$FieldCache[$FieldId];
1173  }
1174 
1185  public function GetFieldByName($FieldName, $IgnoreCase = FALSE)
1186  {
1187  $FieldId = $this->GetFieldIdByName($FieldName, $IgnoreCase);
1188  return ($FieldId === FALSE) ? NULL : $this->GetField($FieldId);
1189  }
1190 
1198  public function GetFieldIdByName($FieldName, $IgnoreCase = FALSE)
1199  {
1200  return $this->GetItemIdByName($FieldName, $IgnoreCase);
1201  }
1202 
1208  public function FieldExists($Field)
1209  {
1210  return is_numeric($Field)
1211  ? $this->ItemExists($Field)
1212  : $this->NameIsInUse($Field);
1213  }
1214 
1228  public function GetFields($FieldTypes = NULL, $OrderType = NULL,
1229  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
1230  {
1231  # create empty array to pass back
1232  $Fields = array();
1233 
1234  # for each field type in database
1235  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields"
1236  ." WHERE SchemaId = ".intval($this->Id)
1237  .(!$IncludeDisabledFields ? " AND Enabled != 0" : "")
1238  .(!$IncludeTempFields ? " AND FieldId >= 0" : ""));
1239  while ($Record = $this->DB->FetchRow())
1240  {
1241  # if field type is known
1242  if (array_key_exists($Record["FieldType"], MetadataField::$FieldTypePHPEnums))
1243  {
1244  # if no specific type requested or if field is of requested type
1245  if (($FieldTypes == NULL)
1246  || (MetadataField::$FieldTypePHPEnums[$Record["FieldType"]]
1247  & $FieldTypes))
1248  {
1249  # create field object and add to array to be passed back
1250  $Fields[$Record["FieldId"]] = $this->GetField($Record["FieldId"]);
1251  }
1252  }
1253  }
1254 
1255  # if field sorting requested
1256  if ($OrderType !== NULL)
1257  {
1258  # update field comparison ordering if not set yet
1259  if (!$this->FieldCompareOrdersSet())
1260  {
1261  $this->UpdateFieldCompareOrders();
1262  }
1263 
1264  $this->FieldCompareType = $OrderType;
1265 
1266  # sort field array by requested order type
1267  uasort($Fields, array($this, "CompareFieldOrder"));
1268  }
1269 
1270  # return array of field objects to caller
1271  return $Fields;
1272  }
1273 
1288  public function GetFieldNames($FieldTypes = NULL, $OrderType = NULL,
1289  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
1290  {
1291  $Fields = $this->GetFields($FieldTypes, $OrderType,
1292  $IncludeDisabledFields, $IncludeTempFields);
1293 
1294  $FieldNames = array();
1295  foreach($Fields as $Field)
1296  {
1297  $FieldNames[$Field->Id()] = $Field->Name();
1298  }
1299 
1300  return $FieldNames;
1301  }
1302 
1321  public function GetFieldsAsOptionList($OptionListName, $FieldTypes = NULL,
1322  $SelectedFieldId = NULL, $IncludeNullOption = TRUE,
1323  $AddEntries = NULL, $AllowMultiple = FALSE, $Disabled = FALSE)
1324  {
1325  # retrieve requested fields
1326  $FieldNames = $this->GetFieldNames($FieldTypes);
1327 
1328  # transform field names to labels
1329  foreach ($FieldNames as $FieldId => $FieldName)
1330  {
1331  $FieldNames[$FieldId] = $this->GetField($FieldId)->GetDisplayName();
1332  }
1333 
1334  # add in null entry if requested
1335  if ($IncludeNullOption)
1336  {
1337  $FieldNames = array("" => "--") + $FieldNames;
1338  }
1339 
1340  # add additional entries if supplied
1341  if ($AddEntries)
1342  {
1343  $FieldNames = $FieldNames + $AddEntries;
1344  }
1345 
1346  # construct option list
1347  $OptList = new HtmlOptionList($OptionListName, $FieldNames, $SelectedFieldId);
1348  $OptList->MultipleAllowed($AllowMultiple);
1349  if ($AllowMultiple)
1350  {
1351  $OptList->Size(min(self::MAX_OPT_LIST_SIZE, count($FieldNames)));
1352  }
1353  $OptList->Disabled($Disabled);
1354 
1355  # return option list HTML to caller
1356  return $OptList->GetHtml();
1357  }
1358 
1364  public function GetFieldTypes()
1365  {
1367  }
1368 
1374  public function GetAllowedFieldTypes()
1375  {
1377  }
1378 
1383  public function RemoveQualifierAssociations($QualifierIdOrObject)
1384  {
1385  # sanitize qualifier ID or grab it from object
1386  $QualifierIdOrObject = is_object($QualifierIdOrObject)
1387  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
1388 
1389  # delete intersection records from database
1390  $this->DB->Query("DELETE FROM FieldQualifierInts"
1391  ." WHERE QualifierId = ".$QualifierIdOrObject);
1392  }
1393 
1399  public function QualifierIsInUse($QualifierIdOrObject)
1400  {
1401  # sanitize qualifier ID or grab it from object
1402  $QualifierIdOrObject = is_object($QualifierIdOrObject)
1403  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
1404 
1405  # determine whether any fields use qualifier as default
1406  $DefaultCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount"
1407  ." FROM MetadataFields"
1408  ." WHERE DefaultQualifier = ".$QualifierIdOrObject,
1409  "RecordCount");
1410 
1411  # determine whether any fields are associated with qualifier
1412  $AssociationCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount"
1413  ." FROM FieldQualifierInts"
1414  ." WHERE QualifierId = ".$QualifierIdOrObject,
1415  "RecordCount");
1416 
1417  # report whether qualifier is in use based on defaults and associations
1418  return (($DefaultCount + $AssociationCount) > 0) ? TRUE : FALSE;
1419  }
1420 
1425  public function GetHighestFieldId()
1426  {
1427  return $this->GetHighestItemId();
1428  }
1429 
1438  public function StdNameToFieldMapping($MappedName, $FieldId = NULL)
1439  {
1440  if (func_num_args() > 1)
1441  {
1442  if (!isset(self::$FieldMappings[$this->Id][$MappedName])
1443  || (self::$FieldMappings[$this->Id][$MappedName] != $FieldId))
1444  {
1445  if (($FieldId !== NULL) && !$this->FieldExists($FieldId))
1446  {
1447  throw new InvalidArgumentException("Attempt to set"
1448  ." standard field mapping to invalid field ID"
1449  ." (".$FieldId.") at ".StdLib::GetMyCaller().".");
1450  }
1451 
1452  # if a mapping is set and is not NULL
1453  if (isset(self::$FieldMappings[$this->Id][$MappedName]))
1454  {
1455  $this->DB->Query("DELETE FROM StandardMetadataFieldMappings"
1456  ." WHERE SchemaId = '".addslashes($this->Id)
1457  ."' AND Name = '".addslashes($MappedName)."'");
1458  unset(self::$FieldMappings[$this->Id][$MappedName]);
1459  }
1460 
1461  if ($FieldId !== NULL)
1462  {
1463  $this->DB->Query("INSERT INTO StandardMetadataFieldMappings"
1464  ." (SchemaId, Name, FieldId) VALUES ('"
1465  .addslashes($this->Id)."', '".addslashes($MappedName)
1466  ."', '".addslashes($FieldId)."')");
1467  self::$FieldMappings[$this->Id][$MappedName] = $FieldId;
1468  }
1469  }
1470  }
1471  return isset(self::$FieldMappings[$this->Id][$MappedName])
1472  ? self::$FieldMappings[$this->Id][$MappedName] : NULL;
1473  }
1474 
1481  public function FieldToStdNameMapping($FieldId)
1482  {
1483  $MappedName = array_search($FieldId, self::$FieldMappings[$this->Id]);
1484  return ($MappedName === FALSE) ? NULL : $MappedName;
1485  }
1486 
1494  public function GetFieldByMappedName($MappedName)
1495  {
1496  return ($this->StdNameToFieldMapping($MappedName) == NULL) ? NULL
1497  : $this->GetField($this->StdNameToFieldMapping($MappedName));
1498  }
1499 
1507  public function GetFieldIdByMappedName($MappedName)
1508  {
1509  return $this->StdNameToFieldMapping($MappedName);
1510  }
1511 
1516  public function GetOwnedFields()
1517  {
1518  $Fields = array();
1519 
1520  $this->DB->Query("SELECT * FROM MetadataFields"
1521  ." WHERE Owner IS NOT NULL AND LENGTH(Owner) > 0"
1522  ." AND SchemaId = ".intval($this->Id));
1523 
1524  while (FALSE !== ($Row = $this->DB->FetchRow()))
1525  {
1526  $FieldId = $Row["FieldId"];
1527  $Fields[$FieldId] = $this->GetField($FieldId);
1528  }
1529 
1530  return $Fields;
1531  }
1532 
1538  public static function FieldExistsInAnySchema($Field)
1539  {
1540  # if we were given a field id, check to see if it exists
1541  self::LoadFieldNamesCache();
1542  if (is_numeric($Field) &&
1543  array_key_exists($Field, self::$FieldNamesCache))
1544  {
1545  return TRUE;
1546  }
1547 
1548  # otherwise, try to look up this field
1549  try
1550  {
1551  $FieldId = self::GetCanonicalFieldIdentifier($Field);
1552  return array_key_exists($FieldId, self::$FieldNamesCache) ?
1553  TRUE : FALSE;
1554  }
1555  catch (Exception $e)
1556  {
1557  # if we can't find the field, then it doesn't exist
1558  return FALSE;
1559  }
1560  }
1561 
1581  public static function GetCanonicalFieldIdentifier($Field, $SchemaId = NULL)
1582  {
1583  # check to make sure any specified schema is valid
1584  self::LoadFieldNamesCache();
1585  if ($SchemaId !== NULL)
1586  {
1587  if (!isset(self::$SchemaNamesCache[$SchemaId]))
1588  {
1589  throw new InvalidArgumentException(
1590  "Invalid schema ID supplied (".$SchemaId.").");
1591  }
1592  }
1593 
1594  # if field object was passed in
1595  if ($Field instanceof MetadataField)
1596  {
1597  # check to make sure field ID is within any specified schema
1598  if (($SchemaId !== NULL) && ($Field->SchemaId() != $SchemaId))
1599  {
1600  throw new Exception("Supplied field (".$Field
1601  .") is not within specified "
1602  .self::$SchemaNamesCache[$SchemaId]
1603  ." schema (".$SchemaId.")");
1604  }
1605 
1606  # return identifier from field to caller
1607  return $Field->Id();
1608  }
1609  # else if field ID was passed in
1610  elseif (is_numeric($Field))
1611  {
1612  # check to make sure field ID is valid
1613  if (!isset(self::$FieldNamesCache[$Field]))
1614  {
1615  throw new InvalidArgumentException(
1616  "Invalid field ID supplied (".$Field.").");
1617  }
1618 
1619  # check to make sure field ID is within any specified schema
1620  if (($SchemaId !== NULL)
1621  && (self::$FieldNamesCache[$Field]["SchemaId"] != $SchemaId))
1622  {
1623  throw new Exception("Supplied field ID (".$Field
1624  .") is not within specified "
1625  .self::$SchemaNamesCache[$SchemaId]
1626  ." schema (".$SchemaId.")");
1627  }
1628 
1629  # return supplied field ID to caller
1630  return (int)$Field;
1631  }
1632  # else if field name was passed in
1633  elseif (is_string($Field))
1634  {
1635  # look for field with specified name
1636  $FieldName = trim($Field);
1637  $FieldId = NULL;
1638  array_walk(self::$FieldNamesCache,
1639  function($Value, $Key, $FieldName) use (&$FieldId, $SchemaId)
1640  {
1641  if (($Value["QualifiedFieldName"] == $FieldName)
1642  && (($SchemaId === NULL)
1643  || ($Value["SchemaId"] == $SchemaId)))
1644  {
1645  $FieldId = $Key;
1646  }
1647  }, $FieldName);
1648 
1649  # if field with specified name not found
1650  if ($FieldId === NULL)
1651  {
1652  # log error and look for field with unqualified version of name
1653  # (NOTE: This is a temporary measure, to be removed once errors
1654  # are no longer regularly showing up in the log, in favor
1655  # of immediately throwing an exception if the name was
1656  # not found.)
1657  $FieldId = NULL;
1658  array_walk(self::$FieldNamesCache,
1659  function($Value, $Key, $FieldName) use (&$FieldId)
1660  {
1661  if ($Value["FieldName"] == $FieldName)
1662  {
1663  $FieldId = $Key;
1664  }
1665  }, $FieldName);
1666  if ($FieldId === NULL)
1667  {
1668  throw new Exception(
1669  "No field found with the name \"".$FieldName."\".");
1670  }
1671  else
1672  {
1673  $GLOBALS["AF"]->LogError(ApplicationFramework::LOGLVL_ERROR,
1674  "No field found with the name \"".$FieldName."\"."
1675  ." TRACE: ".StdLib::GetBacktraceAsString());
1676  }
1677  }
1678 
1679  # return found field ID to caller
1680  return $FieldId;
1681  }
1682  # else error out because we were given an illegal field argument
1683  else
1684  {
1685  throw new InvalidArgumentException(
1686  "Illegal field argument supplied.");
1687  }
1688  }
1689 
1704  public static function GetPrintableFieldName($Field)
1705  {
1706  # retrieve field ID
1707  $Id = self::GetCanonicalFieldIdentifier($Field);
1708 
1709  # if we have a label for this field, return it
1710  self::LoadFieldNamesCache();
1711  if (isset(self::$FieldNamesCache[$Id]))
1712  {
1713  $DisplayName = strlen(self::$FieldNamesCache[$Id]["FieldLabel"]) ?
1714  self::$FieldNamesCache[$Id]["FieldLabel"] :
1715  self::$FieldNamesCache[$Id]["FieldName"] ;
1716  return self::$FieldNamesCache[$Id]["SchemaPrefix"].$DisplayName;
1717  }
1718 
1719  # otherwise return a blank string
1720  return "";
1721  }
1722 
1727  public static function GetStandardFieldNames()
1728  {
1729  $DB = new Database();
1730  $DB->Query("SELECT DISTINCT Name FROM StandardMetadataFieldMappings");
1731  return $DB->FetchColumn("Name");
1732  }
1733 
1741  public static function TranslateLegacySearchValues(
1742  $FieldId, $Values)
1743  {
1744  # start out assuming we won't find any values to translate
1745  $ReturnValues = array();
1746 
1747  # try to grab the specified field
1748  try
1749  {
1750  $Field = new MetadataField($FieldId);
1751  }
1752  catch (Exception $e)
1753  {
1754  # field no longer exists, so there are no values to translate
1755  return $ReturnValues;
1756  }
1757 
1758  # if incoming value is not an array
1759  if (!is_array($Values))
1760  {
1761  # convert incoming value to an array
1762  $Values = array($Values);
1763  }
1764 
1765  # for each incoming value
1766  foreach ($Values as $Value)
1767  {
1768  # look up value for index
1769  if ($Field->Type() == self::MDFTYPE_FLAG)
1770  {
1771  # (for flag fields the value index (0 or 1) is used in Database)
1772  if ($Value >= 0)
1773  {
1774  $ReturnValues[] = "=".$Value;
1775  }
1776  }
1777  elseif ($Field->Type() == self::MDFTYPE_NUMBER)
1778  {
1779  # (for flag fields the value index (0 or 1) is used in Database)
1780  if ($Value >= 0)
1781  {
1782  $ReturnValues[] = ">=".$Value;
1783  }
1784  }
1785  elseif ($Field->Type() == self::MDFTYPE_USER)
1786  {
1787  $User = new CWUser(intval($Value));
1788  if ($User)
1789  {
1790  $ReturnValues[] = "=".$User->Get("UserName");
1791  }
1792  }
1793  elseif ($Field->Type() == self::MDFTYPE_OPTION)
1794  {
1795  if (!isset($PossibleFieldValues))
1796  {
1797  $PossibleFieldValues = $Field->GetPossibleValues();
1798  }
1799 
1800  if (isset($PossibleFieldValues[$Value]))
1801  {
1802  $ReturnValues[] = "=".$PossibleFieldValues[$Value];
1803  }
1804  }
1805  else
1806  {
1807  $NewValue = $Field->GetValueForId($Value);
1808  if ($NewValue !== NULL)
1809  {
1810  $ReturnValues[] = "=".$NewValue;
1811  }
1812  }
1813  }
1814 
1815  # return array of translated values to caller
1816  return $ReturnValues;
1817  }
1818 
1823  public static function GetAllSchemaIds()
1824  {
1825  return array_keys(self::GetAllSchemaNames());
1826  }
1827 
1832  public static function GetAllSchemaNames()
1833  {
1834  $DB = new Database();
1835  $DB->Query("SELECT SchemaId, Name FROM MetadataSchemas");
1836  return $DB->FetchColumn("Name", "SchemaId");
1837  }
1838 
1844  public static function GetAllSchemas()
1845  {
1846  # fetch IDs of all metadata schemas
1847  $SchemaIds = self::GetAllSchemaIds();
1848 
1849  # construct objects from the IDs
1850  $Schemas = array();
1851  foreach ($SchemaIds as $SchemaId)
1852  {
1853  $Schemas[$SchemaId] = new MetadataSchema($SchemaId);
1854  }
1855 
1856  # return schemas to caller
1857  return $Schemas;
1858  }
1859 
1866  public static function FieldUsedInPrivileges($FieldId)
1867  {
1868  # list of priv types we'll be checking
1869  $PrivTypes = array(
1870  "AuthoringPrivileges",
1871  "EditingPrivileges",
1872  "ViewingPrivileges");
1873 
1874  # iterate over each schema
1875  foreach (self::GetAllSchemas() as $Schema)
1876  {
1877  # see if the provided field is checked in any of the
1878  # schema-level privs, returning TRUE if so
1879  foreach ($PrivTypes as $PrivType)
1880  {
1881  if ($Schema->$PrivType()->ChecksField($FieldId))
1882  {
1883  return TRUE;
1884  }
1885  }
1886 
1887  # otherwise, iterate over all the field-level privs, returning true
1888  # if any of those check the provided field
1889  foreach ($Schema->GetFields() as $Field)
1890  {
1891  foreach ($PrivTypes as $PrivType)
1892  {
1893  if ($Field->$PrivType()->ChecksField($FieldId))
1894  {
1895  return TRUE;
1896  }
1897  }
1898  }
1899  }
1900 
1901  # nothing checks this field, return FALSE
1902  return FALSE;
1903  }
1904 
1910  public static function GetSchemaIdForName($Name)
1911  {
1912  $DB = new Database();
1913  $Id = $DB->Query("SELECT SchemaId FROM MetadataSchemas"
1914  ." WHERE Name = '".addslashes($Name)."'", "SchemaId");
1915  return ($Id === FALSE) ? NULL : (int)$Id;
1916  }
1917 
1923  public static function SetOwnerListRetrievalFunction($Callback)
1924  {
1925  if (is_callable($Callback))
1926  {
1927  self::$OwnerListRetrievalFunction = $Callback;
1928  }
1929  }
1930 
1936  public static function NormalizeOwnedFields()
1937  {
1938  # if an owner list retrieval function and default schema exists
1939  if (self::$OwnerListRetrievalFunction
1940  && self::SchemaExistsWithId(self::SCHEMAID_DEFAULT))
1941  {
1942  # retrieve the list of owners that currently exist
1943  $OwnerList = call_user_func(self::$OwnerListRetrievalFunction);
1944 
1945  # an array is expected
1946  if (is_array($OwnerList))
1947  {
1948  $Schema = new MetadataSchema(self::SCHEMAID_DEFAULT);
1949 
1950  # get each metadata field that is owned by a plugin
1951  $OwnedFields = $Schema->GetOwnedFields();
1952 
1953  # loop through each owned field
1954  foreach ($OwnedFields as $OwnedField)
1955  {
1956  # the owner of the current field
1957  $Owner = $OwnedField->Owner();
1958 
1959  # if the owner of the field is in the list of owners that
1960  # currently exist, i.e., available plugins
1961  if (in_array($Owner, $OwnerList))
1962  {
1963  # enable the field and reset its "enable on owner return"
1964  # flag if the "enable on owner return" flag is currently
1965  # set to true. in other words, re-enable the field since
1966  # the owner has returned to the list of existing owners
1967  if ($OwnedField->EnableOnOwnerReturn())
1968  {
1969  $OwnedField->Enabled(TRUE);
1970  $OwnedField->EnableOnOwnerReturn(FALSE);
1971  }
1972  }
1973 
1974  # if the owner of the field is *not* in the list of owners
1975  # that currently exist, i.e., available plugins
1976  else
1977  {
1978  # first, see if the field is currently enabled since it
1979  # will determine whether the field is re-enabled when
1980  # the owner becomes available again
1981  $Enabled = $OwnedField->Enabled();
1982 
1983  # if the field is enabled, set its "enable on owner
1984  # return" flag to true and disable the field. nothing
1985  # needs to be done if the field is already disabled
1986  if ($Enabled)
1987  {
1988  $OwnedField->EnableOnOwnerReturn($Enabled);
1989  $OwnedField->Enabled(FALSE);
1990  }
1991  }
1992  }
1993  }
1994  }
1995  }
1996 
2001  protected function UpdateFieldCompareOrders()
2002  {
2003  $Index = 0;
2004 
2005  foreach ($this->GetDisplayOrder()->GetFields() as $Field)
2006  {
2007  $this->FieldCompareDisplayOrder[$Field->Id()] = $Index++;
2008  }
2009 
2010  $Index = 0;
2011 
2012  foreach ($this->GetEditOrder()->GetFields() as $Field)
2013  {
2014  $this->FieldCompareEditOrder[$Field->Id()] = $Index++;
2015  }
2016  }
2017 
2022  public function GetDisplayOrder()
2023  {
2024  return MetadataFieldOrder::GetOrderForSchema($this, self::ORDER_DISPLAY_NAME);
2025  }
2026 
2031  public function GetEditOrder()
2032  {
2033  return MetadataFieldOrder::GetOrderForSchema($this, self::ORDER_EDIT_NAME);
2034  }
2035 
2040  protected function FieldCompareOrdersSet()
2041  {
2042  return $this->FieldCompareDisplayOrder && $this->FieldCompareEditOrder;
2043  }
2044 
2052  protected function CompareFieldOrder($FieldA, $FieldB)
2053  {
2054  if ($this->FieldCompareType == self::MDFORDER_ALPHABETICAL)
2055  {
2056  return ($FieldA->GetDisplayName() < $FieldB->GetDisplayName()) ? -1 : 1;
2057  }
2058 
2059  if ($this->FieldCompareType == self::MDFORDER_EDITING)
2060  {
2062  }
2063 
2064  else
2065  {
2067  }
2068 
2069  $PositionA = GetArrayValue($Order, $FieldA->Id(), 0);
2070  $PositionB = GetArrayValue($Order, $FieldB->Id(), 0);
2071 
2072  return $PositionA < $PositionB ? -1 : 1;
2073  }
2074 
2075 
2079  public static function ClearStaticCaches()
2080  {
2081  self::$FieldCache = NULL;
2082  self::$FieldNamesCache = NULL;
2083  }
2084 
2085  # ---- PRIVATE INTERFACE -------------------------------------------------
2086 
2087  private $AuthoringPrivileges;
2088  private $EditingPrivileges;
2089  private $ErrorMsgs = array();
2090  private $FieldCompareType;
2091  private $Id;
2092  private $NewFields = array();
2093  private $ViewingPrivileges;
2094  private $ViewPage;
2095 
2096  private static $FieldMappings;
2097  private static $ValueCache = NULL;
2098 
2099  private static $FieldCache = NULL;
2100  private static $FieldNamesCache;
2101  private static $SchemaNamesCache;
2102 
2104 
2108  protected $FieldCompareDisplayOrder = array();
2109 
2113  protected $FieldCompareEditOrder = array();
2114 
2118  private static function LoadFieldNamesCache()
2119  {
2120  if (!isset(self::$FieldNamesCache))
2121  {
2122  self::$SchemaNamesCache = self::GetAllSchemaNames();
2123 
2124  $DB = new Database();
2125  $DB->Query("SELECT SchemaId, FieldId, FieldName, Label FROM MetadataFields"
2126  # (NOTE: This ordering is a temporary measure, to be removed when the
2127  # the error logging is removed from GetCanonicalFieldIdentifier().)
2128  ." ORDER BY SchemaId DESC");
2129  while ($Row = $DB->FetchRow())
2130  {
2131  $SchemaPrefix = ($Row["SchemaId"] == self::SCHEMAID_DEFAULT)
2132  ? "" : self::$SchemaNamesCache[$Row["SchemaId"]].": ";
2133 
2134  $TrimmedLabel = trim($Row["Label"]);
2135  $TrimmedName = trim($Row["FieldName"]);
2136 
2137  self::$FieldNamesCache[$Row["FieldId"]] = [
2138  "SchemaId" => $Row["SchemaId"],
2139  "SchemaPrefix" => $SchemaPrefix,
2140  "FieldName" => $TrimmedName,
2141  "QualifiedFieldName" => $SchemaPrefix.$TrimmedName,
2142  "FieldLabel" => $TrimmedLabel,
2143  ];
2144  }
2145  }
2146  }
2147 
2155  private function ConvertXmlToPrivilegeSet($Xml)
2156  {
2157  # clear any existing errors
2158  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
2159  { unset($this->ErrorMsgs[__METHOD__]); }
2160 
2161  # create new privilege set
2162  $PrivSet = new PrivilegeSet();
2163 
2164  # for each XML child
2165  foreach ($Xml as $Tag => $Value)
2166  {
2167  # take action based on element name
2168  switch ($Tag)
2169  {
2170  case "PrivilegeSet":
2171  # convert child data to new set
2172  $NewSet = $this->ConvertXmlToPrivilegeSet($Value);
2173 
2174  # add new set to our privilege set
2175  $PrivSet->AddSet($NewSet);
2176  break;
2177 
2178  case "AddCondition":
2179  # start with default values for optional parameters
2180  unset($ConditionField);
2181  $ConditionValue = NULL;
2182  $ConditionOperator = "==";
2183 
2184  # pull out parameters
2185  foreach ($Value as $ParamName => $ParamValue)
2186  {
2187  $ParamValue = trim($ParamValue);
2188  switch ($ParamName)
2189  {
2190  case "Field":
2191  $ConditionField = $this->GetField($ParamValue);
2192  if ($ConditionField === NULL)
2193  {
2194  # record error about unknown field
2195  $this->ErrorMsgs[__METHOD__][] =
2196  "Unknown metadata field name found"
2197  ." in AddCondition (".$ParamValue.").";
2198 
2199  # bail out
2200  return NULL;
2201  }
2202  break;
2203 
2204  case "Value":
2205  $ConditionValue = (string)$ParamValue;
2206 
2207  if ($ConditionValue == "NULL")
2208  {
2209  $ConditionValue = NULL;
2210  }
2211  elseif ($ConditionValue == "TRUE")
2212  {
2213  $ConditionValue = TRUE;
2214  }
2215  elseif ($ConditionValue == "FALSE")
2216  {
2217  $ConditionValue = FALSE;
2218  }
2219  break;
2220 
2221  case "Operator":
2222  $ConditionOperator = (string)$ParamValue;
2223  break;
2224 
2225  default:
2226  # record error about unknown parameter name
2227  $this->ErrorMsgs[__METHOD__][] =
2228  "Unknown tag found in AddCondition ("
2229  .$ParamName.").";
2230 
2231  # bail out
2232  return NULL;
2233  break;
2234  }
2235  }
2236 
2237  # if no field value
2238  if (!isset($ConditionField))
2239  {
2240  # record error about no field value
2241  $this->ErrorMsgs[__METHOD__][] =
2242  "No metadata field specified in AddCondition.";
2243 
2244  # bail out
2245  return NULL;
2246  }
2247 
2248  # if this is a vocabulary field
2249  $Factory = $ConditionField instanceof MetadataField ?
2250  $ConditionField->GetFactory() : NULL;
2251  if ($Factory !== NULL)
2252  {
2253  # look up the id of the provided value
2254  $ConditionValue = $Factory->GetItemIdByName(
2255  $ConditionValue);
2256 
2257  # if none was found, error out
2258  if ($ConditionValue === FALSE)
2259  {
2260  $this->ErrorMsgs[__METHOD__][] =
2261  "Invalid value for field specified in AddCondition.";
2262  return NULL;
2263  }
2264  }
2265 
2266  # add conditional to privilege set
2267  $PrivSet->AddCondition($ConditionField,
2268  $ConditionValue, $ConditionOperator);
2269  break;
2270 
2271  default:
2272  # strip any excess whitespace off of value
2273  $Value = trim($Value);
2274 
2275  # if child looks like valid method name
2276  if (method_exists("PrivilegeSet", $Tag))
2277  {
2278  # convert constants if needed
2279  if (defined($Value))
2280  {
2281  $Value = constant($Value);
2282  }
2283  # convert booleans if needed
2284  elseif (strtoupper($Value) == "TRUE")
2285  {
2286  $Value = TRUE;
2287  }
2288  elseif (strtoupper($Value) == "FALSE")
2289  {
2290  $Value = FALSE;
2291  }
2292  # convert privilege flag names if needed and appropriate
2293  elseif (preg_match("/Privilege$/", $Tag))
2294  {
2295  static $Privileges;
2296  if (!isset($Privileges))
2297  {
2298  $PFactory = new PrivilegeFactory();
2299  $Privileges = $PFactory->GetPrivileges(TRUE, FALSE);
2300  }
2301  if (in_array($Value, $Privileges))
2302  {
2303  $Value = array_search($Value, $Privileges);
2304  }
2305  }
2306 
2307  # set value using child data
2308  $PrivSet->$Tag((string)$Value);
2309  }
2310  else
2311  {
2312  # record error about bad tag
2313  $this->ErrorMsgs[__METHOD__][] =
2314  "Unknown tag encountered (".$Tag.").";
2315 
2316  # bail out
2317  return NULL;
2318  }
2319  break;
2320  }
2321  }
2322 
2323  # return new privilege set to caller
2324  return $PrivSet;
2325  }
2326 
2334  protected function UpdateValue($ColumnName, $NewValue = DB_NOVALUE)
2335  {
2336  return $this->DB->UpdateValue("MetadataSchemas", $ColumnName, $NewValue,
2337  "SchemaId = ".intval($this->Id),
2338  self::$ValueCache[$this->Id]);
2339  }
2340 }
const MDFSTAT_ILLEGALLABEL
const ORDER_DISPLAY_NAME
GetHighestItemId($IgnoreSqlCondition=FALSE)
Retrieve highest item ID in use.
const LOGLVL_ERROR
ERROR error logging level.
static $FieldTypeDBEnums
const RESOURCENAME_DEFAULT
static $FieldTypeDBAllowedEnums
GetFieldIdByName($FieldName, $IgnoreCase=FALSE)
Retrieve metadata field ID by name.
GetHighestFieldId()
Get highest field ID currently in use.
const SCHEMAID_RESOURCES
$FieldCompareEditOrder
The cache for metadata field edit ordering.
GetViewPageIdParameter()
Get the resource ID GET parameter for the view page for the schema.
GetAllowedFieldTypes()
Retrieve array of field types that user can create.
ViewingPrivileges(PrivilegeSet $NewValue=NULL)
Get/set privileges that allowing viewing resources with this schema.
Name($NewValue=DB_NOVALUE)
Get/set name of schema.
Metadata schema (in effect a Factory class for MetadataField).
ErrorMessages($Method=NULL)
Get error messages (if any) from recent calls.
static $FieldTypePHPEnums
static TranslateLegacySearchValues($FieldId, $Values)
Translate search values from a legacy URL string to their modern equivalents.
UserCanView($User)
Determine if the given user can view resources using this schema.
const MDFORDER_ALPHABETICAL
UserCanEdit($User)
Determine if the given user can edit resources using this schema.
static Create($SchemaId, $FieldType, $FieldName, $Optional=NULL, $DefaultValue=NULL)
Create a new metadata field.
GetItemIdByName($Name, $IgnoreCase=FALSE)
Retrieve item ID by name.
static NormalizeOwnedFields()
Disable owned fields that have an owner that is unavailable and re-enable fields if an owner has retu...
SQL database abstraction object with smart query caching.
Definition: Database.php:22
ViewPage($NewValue=DB_NOVALUE)
Get/set name of page to go to for viewing resources using this schema.
UserCanAuthor($User)
Determine if the given user can author resources using this schema.
EditingPrivileges(PrivilegeSet $NewValue=NULL)
Get/set privileges that allowing editing resources with this schema.
GetFactory()
Retrieve item factory object for this field.
GetFieldByName($FieldName, $IgnoreCase=FALSE)
Retrieve metadata field by name.
static GetPrintableFieldName($Field)
Retrieve label for field.
static SchemaExistsWithId($SchemaId)
Check with schema exists with specified ID.
Factory which extracts all defined privileges from the database.
static GetCanonicalFieldIdentifier($Field, $SchemaId=NULL)
Retrieve canonical identifier for field.
const MDFSTAT_FIELDDOESNOTEXIST
GetFieldsAsOptionList($OptionListName, $FieldTypes=NULL, $SelectedFieldId=NULL, $IncludeNullOption=TRUE, $AddEntries=NULL, $AllowMultiple=FALSE, $Disabled=FALSE)
Retrieve fields of specified type as HTML option list with field names as labels and field IDs as val...
CompareFieldOrder($FieldA, $FieldB)
Field sorting callback.
GetFieldIdByMappedName($MappedName)
Get field ID by standard field name.
static GetMyCaller()
Get string with file and line number for call to current function.
Definition: StdLib.php:44
Set of privileges used to access resource information or other parts of the system.
GetFieldNames($FieldTypes=NULL, $OrderType=NULL, $IncludeDisabledFields=FALSE, $IncludeTempFields=FALSE)
Retrieve field names.
Delete()
Destroy metadata schema.
static FieldUsedInPrivileges($FieldId)
Determine if a specified field is used in either schema or field permissions.
AddFieldFromXml($Xml)
Add new metadata field based on supplied XML.
static GetStandardFieldNames()
Retrieve a list of all available standard fields names.
static SetOwnerListRetrievalFunction($Callback)
Allow external dependencies, i.e., the current list of owners that are available, to be injected...
StdNameToFieldMapping($MappedName, $FieldId=NULL)
Get/set mapping of standard field name to specific field.
ItemExists($ItemId, $IgnoreSqlCondition=FALSE)
Check that item exists with specified ID.
FieldToStdNameMapping($FieldId)
Get mapping of field ID to standard field name.
const MDFTYPE_CONTROLLEDNAME
static GetAllSchemaIds()
Get IDs for all existing metadata schemas.
AddFieldsFromXmlFile($FileName, $Owner=NULL, $TestRun=FALSE)
Add new metadata fields from XML file.
GetOwnedFields()
Get fields that have an owner associated with them.
PathMatchesViewPage($Path)
Determine if a path matches the view page path for the schema.
static GetAllSchemaNames()
Get names for all existing metadata schemas.
const MDFSTAT_DUPLICATEDBCOLUMN
static GetBacktraceAsString($IncludeArgs=TRUE)
Get backtrace as a string.
Definition: StdLib.php:130
const DB_NOVALUE
Definition: Database.php:1784
ClearCaches()
Clear item information caches.
GetEditOrder()
Get the editing order for the schema.
static FieldExistsInAnySchema($Field)
Determine if a Field exists in any schema.
static $OwnerListRetrievalFunction
const MDFSTAT_DUPLICATELABEL
static Create(MetadataSchema $Schema, $Name, array $FieldOrder=array())
Create a new metadata field order, optionally specifying the order of the fields. ...
NewFields()
Get new fields recently added (if any) via XML file.
AddField($FieldName, $FieldType, $Optional=TRUE, $DefaultValue=NULL)
Add new metadata field.
GetFields($FieldTypes=NULL, $OrderType=NULL, $IncludeDisabledFields=FALSE, $IncludeTempFields=FALSE)
Retrieve array of fields.
GetDisplayOrder()
Get the display order for the schema.
FieldCompareOrdersSet()
Determine whether the field comparison ordering caches are set.
static Create($Name, PrivilegeSet $AuthorPrivs=NULL, PrivilegeSet $EditPrivs=NULL, PrivilegeSet $ViewPrivs=NULL, $ViewPage="", $ResourceName=NULL)
Create new metadata schema.
Object representing a locally-defined type of metadata field.
AuthoringPrivileges(PrivilegeSet $NewValue=NULL)
Get/set privileges that allowing authoring resources with this schema.
__construct($SchemaId=self::SCHEMAID_DEFAULT)
Object constructor, used to load an existing schema.
AbbreviatedName($NewValue=DB_NOVALUE)
Get/set abbreviated name of schema.
FieldExists($Field)
Check whether field with specified name exists.
Represents a "resource" in CWIS.
Definition: Resource.php:13
DropField($FieldId)
Delete metadata field and all associated data.
RemoveQualifierAssociations($QualifierIdOrObject)
Remove all metadata field associations for a given qualifier.
static Singularize($Word)
Singularize an English word.
Definition: StdLib.php:200
UpdateFieldCompareOrders()
Update the field comparison ordering cache that is used for sorting fields.
static GetOrderForSchema(MetadataSchema $Schema, $Name)
Get a metadata field order with a specific name for a given metadata schema.
const MDFSTAT_ILLEGALNAME
static GetOrdersForSchema(MetadataSchema $Schema)
Get all of the orders associated with a schema.
GetField($FieldId)
Retrieve metadata field.
GetFieldByMappedName($MappedName)
Get field by standard field name.
Id()
Get schema ID.
static GetConstantName($Value, $Prefix=NULL)
Get name (string) for constant.
Common factory class for item manipulation.
Definition: ItemFactory.php:17
const MDFSTAT_DUPLICATENAME
static GetSchemaIdForName($Name)
Get schema ID for specified name.
UpdateValue($ColumnName, $NewValue=DB_NOVALUE)
Convenience function to supply parameters to Database->UpdateValue().
$FieldCompareDisplayOrder
The cache for metadata field display ordering.
ResourceName($NewValue=DB_NOVALUE)
Get/set name of resources using this schema.
NameIsInUse($Name, $IgnoreCase=FALSE)
Check whether item name is currently in use.
Factory for Resource objects.
CWIS-specific user class.
Definition: CWUser.php:13
GetFieldTypes()
Retrieve array of field types.
static GetAllSchemas()
Get all existing metadata schemas.
Convenience class for generating an HTML select/option form element.
QualifierIsInUse($QualifierIdOrObject)
Check whether qualifier is in use by any metadata field (in any schema).
static ClearStaticCaches()
Clear internal caches.