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 
77  public function __construct($SchemaId = self::SCHEMAID_DEFAULT)
78  {
79  # set up item factory base class
80  parent::__construct("MetadataField", "MetadataFields",
81  "FieldId", "FieldName", FALSE,
82  "SchemaId = ".intval($SchemaId));
83 
84  # make sure schema info cache is loaded
85  if (self::$ValueCache === NULL)
86  {
87  $this->DB->Query("SELECT * FROM MetadataSchemas");
88  self::$ValueCache = array();
89  foreach ($this->DB->FetchRows() as $Row)
90  {
91  self::$ValueCache[$Row["SchemaId"]] = $Row;
92  }
93  }
94 
95  # if standard field mappings have not yet been loaded
96  if (!isset(self::$FieldMappings))
97  {
98  # load metadata field IDs to check against
99  $this->DB->Query("SELECT SchemaId, FieldId"
100  ." FROM MetadataFields");
101  $FieldSchemaIds = $this->DB->FetchColumn("SchemaId", "FieldId");
102 
103  # for each standard field mapping
104  $this->DB->Query("SELECT * FROM StandardMetadataFieldMappings");
105  foreach ($this->DB->FetchRows() as $Row)
106  {
107  # if mapping is for a valid field in appropriate schema
108  if (isset($FieldSchemaIds[$Row["FieldId"]])
109  && ($FieldSchemaIds[$Row["FieldId"]] == $Row["SchemaId"]))
110  {
111  # save mapping
112  self::$FieldMappings[$Row["SchemaId"]][$Row["Name"]] =
113  $Row["FieldId"];
114  }
115  else
116  {
117  # error out
118  throw new Exception("Standard field mapping for"
119  ." \"".$Row["Name"]."\" found with"
120  ." invalid schema/field ID combination"
121  ." (".$Row["SchemaId"]."/".$Row["FieldId"].").");
122  }
123  }
124  }
125 
126  # make sure specified schema ID is valid
127  if (!isset(self::$ValueCache[$SchemaId]))
128  {
129  throw new InvalidArgumentException("Attempt to load metadata schema"
130  ." with invalid ID (".$SchemaId.") at "
131  .StdLib::GetMyCaller().".");
132  }
133 
134  # load schema info from cache
135  $Info = self::$ValueCache[$SchemaId];
136  $this->Id = $SchemaId;
137  $this->AuthoringPrivileges = new PrivilegeSet($Info["AuthoringPrivileges"]);
138  $this->EditingPrivileges = new PrivilegeSet($Info["EditingPrivileges"]);
139  $this->ViewingPrivileges = new PrivilegeSet($Info["ViewingPrivileges"]);
140  $this->ViewPage = $Info["ViewPage"];
141  if (!isset(self::$FieldMappings[$this->Id]))
142  {
143  self::$FieldMappings[$this->Id] = array();
144  }
145  }
146 
156  public static function GetConstantName($Value, $Prefix = NULL)
157  {
158  # retrieve all constants for class
159  $Reflect = new ReflectionClass(get_class());
160  $Constants = $Reflect->getConstants();
161 
162  # for each constant
163  foreach ($Constants as $CName => $CValue)
164  {
165  # if value matches and prefix (if supplied) matches
166  if (($CValue == $Value)
167  && (($Prefix === NULL) || (strpos($CName, $Prefix) === 0)))
168  {
169  # return name to caller
170  return $CName;
171  }
172  }
173 
174  # report to caller that no matching constant was found
175  return NULL;
176  }
177 
195  public static function Create($Name,
196  PrivilegeSet $AuthorPrivs = NULL,
197  PrivilegeSet $EditPrivs = NULL,
198  PrivilegeSet $ViewPrivs = NULL,
199  $ViewPage = "",
200  $ResourceName = NULL)
201  {
202  # supply privilege settings if none provided
203  if ($AuthorPrivs === NULL) { $AuthorPrivs = new PrivilegeSet(); }
204  if ($EditPrivs === NULL) { $EditPrivs = new PrivilegeSet(); }
205  if ($ViewPrivs === NULL) { $ViewPrivs = new PrivilegeSet(); }
206 
207  # add schema to database
208  $DB = new Database;
209  if (strtoupper($Name) == "RESOURCES")
210  {
211  $Id = self::SCHEMAID_DEFAULT;
212  }
213  elseif (strtoupper($Name) == "USER")
214  {
215  $Id = self::SCHEMAID_USER;
216  }
217  else
218  {
219  $Id = $DB->Query("SELECT SchemaId FROM MetadataSchemas"
220  ." ORDER BY SchemaId DESC LIMIT 1", "SchemaId") + 1;
221  }
222  $DB->Query("INSERT INTO MetadataSchemas"
223  ." (SchemaId, Name, ViewPage,"
224  ." AuthoringPrivileges, EditingPrivileges, ViewingPrivileges)"
225  ." VALUES (".intval($Id).","
226  ."'".addslashes($Name)."',"
227  ."'".$DB->EscapeString($ViewPage)."',"
228  ."'".$DB->EscapeString($AuthorPrivs->Data())."',"
229  ."'".$DB->EscapeString($EditPrivs->Data())."',"
230  ."'".$DB->EscapeString($ViewPrivs->Data())."')");
231 
232  # clear schema data cache so it will be reloaded
233  self::$ValueCache = NULL;
234 
235  # construct the new schema
236  $Schema = new MetadataSchema($Id);
237 
238  # set schema name if none supplied
239  if (!strlen($Name))
240  {
241  $Schema->Name("Metadata Schema ".$Id);
242  }
243 
244  # set the resource name if one is supplied
245  if ($ResourceName === NULL)
246  {
247  $ResourceName = StdLib::Singularize($Name);
248  }
249  $Schema->ResourceName($ResourceName);
250 
251  # create display and edit orders
252  MetadataFieldOrder::Create($Schema, self::ORDER_DISPLAY_NAME, array() );
253  MetadataFieldOrder::Create($Schema, self::ORDER_EDIT_NAME, array() );
254 
255  # return the new schema
256  return $Schema;
257  }
258 
263  public function Delete()
264  {
265  # delete resources associated with schema
266  $RFactory = new ResourceFactory($this->Id);
267  $ResourceIds = $RFactory->GetItemIds();
268  foreach ($ResourceIds as $ResourceId)
269  {
270  $Resource = new Resource($ResourceId);
271  $Resource->Delete();
272  }
273 
274  # unmap all the mapped fields
275  $MappedNames = array_keys(self::$FieldMappings[$this->Id]);
276  foreach ($MappedNames as $MappedName)
277  {
278  $this->StdNameToFieldMapping($MappedName, NULL);
279  }
280 
281  # delete fields associated with schema
282  $Fields = $this->GetFields(NULL, NULL, TRUE, TRUE);
283  foreach ($Fields as $FieldId => $Field)
284  {
285  $this->DropField($FieldId);
286  }
287 
288  # delete metadata field orders associated with schema
289  foreach (MetadataFieldOrder::GetOrdersForSchema($this) as $Order)
290  {
291  $Order->Delete();
292  }
293 
294  # remove schema info from database
295  $this->DB->Query("DELETE FROM MetadataSchemas WHERE SchemaId = "
296  .intval($this->Id));
297  }
298 
304  public static function SchemaExistsWithId($SchemaId)
305  {
306  if ($SchemaId === NULL) { return FALSE; }
307  $DB = new Database();
308  $DB->Query("SELECT * FROM MetadataSchemas"
309  ." WHERE SchemaId = ".intval($SchemaId));
310  return ($DB->NumRowsSelected() > 0) ? TRUE : FALSE;
311  }
312 
318  public function Id()
319  {
320  # return value to caller
321  return intval($this->Id);
322  }
323 
329  public function Name($NewValue = DB_NOVALUE)
330  {
331  return $this->UpdateValue("Name", $NewValue);
332  }
333 
341  public function AbbreviatedName($NewValue = DB_NOVALUE)
342  {
343  $AName = $this->UpdateValue("AbbreviatedName", $NewValue);
344  if (!strlen($AName))
345  {
346  $AName = strtoupper(substr($this->Name(), 0, 1));
347  }
348  return $AName;
349  }
350 
356  public function ResourceName($NewValue = DB_NOVALUE)
357  {
358  $RName = $this->UpdateValue("ResourceName", $NewValue);
359  if (!strlen($RName))
360  {
361  $RName = self::RESOURCENAME_DEFAULT;
362  }
363  return $RName;
364  }
365 
371  public function ViewPage($NewValue = DB_NOVALUE)
372  {
373  return $this->UpdateValue("ViewPage", $NewValue);
374  }
375 
381  public function AuthoringPrivileges(PrivilegeSet $NewValue = NULL)
382  {
383  # if new privileges supplied
384  if ($NewValue !== NULL)
385  {
386  # store new privileges in database
387  $this->UpdateValue("AuthoringPrivileges", $NewValue->Data());
388  $this->AuthoringPrivileges = $NewValue;
389  }
390 
391  # return current value to caller
392  return $this->AuthoringPrivileges;
393  }
394 
400  public function EditingPrivileges(PrivilegeSet $NewValue = NULL)
401  {
402  # if new privileges supplied
403  if ($NewValue !== NULL)
404  {
405  # store new privileges in database
406  $this->UpdateValue("EditingPrivileges", $NewValue->Data());
407  $this->EditingPrivileges = $NewValue;
408  }
409 
410  # return current value to caller
411  return $this->EditingPrivileges;
412  }
413 
419  public function ViewingPrivileges(PrivilegeSet $NewValue = NULL)
420  {
421  # if new privileges supplied
422  if ($NewValue !== NULL)
423  {
424  # store new privileges in database
425  $this->UpdateValue("ViewingPrivileges", $NewValue->Data());
426  $this->ViewingPrivileges = $NewValue;
427  }
428 
429  # return current value to caller
430  return $this->ViewingPrivileges;
431  }
432 
440  public function UserCanAuthor($User)
441  {
442  # get authoring privilege set for schema
443  $AuthorPrivs = $this->AuthoringPrivileges();
444 
445  # user can author if privileges are greater than resource set
446  $CanAuthor = $AuthorPrivs->MeetsRequirements($User);
447 
448  # allow plugins to modify result of permission check
449  $SignalResult = $GLOBALS["AF"]->SignalEvent(
450  "EVENT_RESOURCE_AUTHOR_PERMISSION_CHECK", array(
451  "Schema" => $this,
452  "User" => $User,
453  "CanAuthor" => $CanAuthor));
454  $CanAuthor = $SignalResult["CanAuthor"];
455 
456  # report back to caller whether user can author field
457  return $CanAuthor;
458  }
459 
467  public function UserCanEdit($User)
468  {
469  # get editing privilege set for schema
470  $EditPrivs = $this->EditingPrivileges();
471 
472  # user can edit if privileges are greater than resource set
473  $CanEdit = $EditPrivs->MeetsRequirements($User);
474 
475  # allow plugins to modify result of permission check
476  $SignalResult = $GLOBALS["AF"]->SignalEvent(
477  "EVENT_RESOURCE_EDIT_PERMISSION_CHECK", array(
478  "Resource" => NULL,
479  "User" => $User,
480  "CanEdit" => $CanEdit,
481  "Schema" => $this, ));
482  $CanEdit = $SignalResult["CanEdit"];
483 
484  # report back to caller whether user can edit field
485  return $CanEdit;
486  }
487 
495  public function UserCanView($User)
496  {
497  # get viewing privilege set for schema
498  $ViewPrivs = $this->ViewingPrivileges();
499 
500  # user can view if privileges are greater than resource set
501  $CanView = $ViewPrivs->MeetsRequirements($User);
502 
503  # allow plugins to modify result of permission check
504  $SignalResult = $GLOBALS["AF"]->SignalEvent(
505  "EVENT_RESOURCE_VIEW_PERMISSION_CHECK", array(
506  "Resource" => NULL,
507  "User" => $User,
508  "CanView" => $CanView,
509  "Schema" => $this, ));
510  $CanView = $SignalResult["CanView"];
511 
512  # report back to caller whether user can view field
513  return $CanView;
514  }
515 
521  public function GetViewPageIdParameter()
522  {
523  # get the query/GET parameters for the view page
524  $Query = parse_url($this->ViewPage(), PHP_URL_QUERY);
525 
526  # the URL couldn't be parsed
527  if (!is_string($Query))
528  {
529  return NULL;
530  }
531 
532  # parse the GET parameters out of the query string
533  $GetVars = ParseQueryString($Query);
534 
535  # search for the ID parameter
536  $Result = array_search("\$ID", $GetVars);
537 
538  return $Result !== FALSE ? $Result : NULL;
539  }
540 
553  public function PathMatchesViewPage($Path)
554  {
555  # get the query/GET parameters for the view page
556  $Query = parse_url($this->ViewPage(), PHP_URL_QUERY);
557 
558  # can't perform matching if the URL couldn't be parsed
559  if (!is_string($Query))
560  {
561  return FALSE;
562  }
563 
564  # parse the GET parameters out of the query string
565  $GetVars = ParseQueryString($Query);
566 
567  # now, get the query/GET parameters from the path given
568  $PathQuery = parse_url($Path, PHP_URL_QUERY);
569 
570  # can't perform matching if the URL couldn't be parsed
571  if (!is_string($PathQuery))
572  {
573  return FALSE;
574  }
575 
576  # parse the GET parameters out of the path's query string
577  $PathGetVars = ParseQueryString($PathQuery);
578 
579  # make sure the given path GET parameters contain at least the GET
580  # parameters from the view page and that all non-variable parameters are
581  # equal. the path GET parameters may contain more, which is okay
582  foreach ($GetVars as $GetVarName => $GetVarValue)
583  {
584  # there's a required parameter that is not included in the path GET
585  # parameters
586  if (!array_key_exists($GetVarName, $PathGetVars))
587  {
588  return FALSE;
589  }
590 
591  # require the path's value to be equal to the view page's value if
592  # the view page's value is not a variable,
593  if ($PathGetVars[$GetVarName] != $GetVarValue
594  && (!strlen($GetVarValue) || $GetVarValue{0} != "$"))
595  {
596  return FALSE;
597  }
598  }
599 
600  # the path matches the view page path
601  return TRUE;
602  }
603 
613  public function AddField(
614  $FieldName, $FieldType, $Optional = TRUE, $DefaultValue = NULL)
615  {
616  # clear any existing error messages
617  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
618  { unset($this->ErrorMsgs[__METHOD__]); }
619 
620  # create new field
621  try
622  {
623  $Field = MetadataField::Create($this->Id(), $FieldType,
624  $FieldName, $Optional, $DefaultValue);
625  }
626  catch (Exception $Exception)
627  {
628  $this->ErrorMsgs[__METHOD__][] = $Exception->getMessage();
629  $Field = NULL;
630  }
631 
632  # clear internal caches to make sure new field is recognized going forward
633  $this->ClearCaches();
634 
635  # return new field to caller
636  return $Field;
637  }
638 
653  public function AddFieldsFromXmlFile($FileName, $Owner = NULL, $TestRun = FALSE)
654  {
655  # clear loading status
656  $this->NewFields = array();
657  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
658  { unset($this->ErrorMsgs[__METHOD__]); }
659 
660  # check that file exists and is readable
661  if (!file_exists($FileName))
662  {
663  $this->ErrorMsgs[__METHOD__][] = "Could not find XML file '"
664  .$FileName."'.";
665  return FALSE;
666  }
667  elseif (!is_readable($FileName))
668  {
669  $this->ErrorMsgs[__METHOD__][] = "Could not read from XML file '"
670  .$FileName."'.";
671  return FALSE;
672  }
673 
674  # load XML from file
675  libxml_use_internal_errors(TRUE);
676  $XmlData = simplexml_load_file($FileName);
677  $Errors = libxml_get_errors();
678  libxml_use_internal_errors(FALSE);
679 
680  # if XML load failed
681  if ($XmlData === FALSE)
682  {
683  # retrieve XML error messages
684  foreach ($Errors as $Err)
685  {
686  $ErrType = ($Err->level == LIBXML_ERR_WARNING) ? "Warning"
687  : (($Err->level == LIBXML_ERR_WARNING) ? "Error"
688  : "Fatal Error");
689  $this->ErrorMsgs[__METHOD__][] = "XML ".$ErrType.": ".$Err->message
690  ." (".$Err->file.":".$Err->line.",".$Err->column.")";
691  }
692  }
693  # else if no metadata fields found record error message
694  elseif (!count($XmlData->MetadataField))
695  {
696  $this->ErrorMsgs[__METHOD__][] = "No metadata fields found.";
697  }
698  # else process metadata fields
699  else
700  {
701  # for each metadata field entry found
702  $FieldsAdded = 0;
703  $FieldIndex = 0;
704  foreach ($XmlData->MetadataField as $FieldXml)
705  {
706  $FieldIndex++;
707 
708  # pull out field type if present
709  if (isset($FieldXml->Type))
710  {
711  $FieldType = "MetadataSchema::".$FieldXml->Type;
712  if (!defined($FieldType))
713  {
714  $FieldType = "MetadataSchema::MDFTYPE_"
715  .strtoupper(preg_replace("/\\s+/", "",
716  $FieldXml->Type));
717  }
718  }
719 
720  # if required values are missing
721  if (!isset($FieldXml->Name) || !isset($FieldXml->Type)
722  || !defined($FieldType))
723  {
724  # add error message about required value missing
725  if (!isset($FieldXml->Name))
726  {
727  $this->ErrorMsgs[__METHOD__][] =
728  "Field name not found (MetadataField #"
729  .$FieldIndex.").";
730  }
731  if (!isset($FieldXml->Type) || !defined($FieldType))
732  {
733  $this->ErrorMsgs[__METHOD__][] =
734  "Valid type not found for field '"
735  .$FieldXml->Name."' (MetadataField #"
736  .$FieldIndex.").";
737  }
738  }
739  # else if there is not already a field with this name
740  elseif (!$this->NameIsInUse(trim($FieldXml->Name)))
741  {
742  # create new field
743  $Field = $this->AddField($FieldXml->Name, constant($FieldType));
744 
745  # if field creation failed
746  if ($Field === NULL)
747  {
748  # add any error message to our error list
749  $ErrorMsgs = $this->ErrorMessages("AddField");
750  foreach ($ErrorMsgs as $Msg)
751  {
752  $this->ErrorMsgs[__METHOD__][] =
753  $Msg." (AddField)";
754  }
755  }
756  else
757  {
758  # add field to list of created fields
759  $this->NewFields[$Field->Id()] = $Field;
760 
761  # assume no vocabulary to load
762  $VocabToLoad = NULL;
763 
764  # for other field attributes
765  foreach ($FieldXml as $MethodName => $Value)
766  {
767  # if tags look valid and have not already been set
768  if (method_exists($Field, $MethodName)
769  && ($MethodName != "Name")
770  && ($MethodName != "Type"))
771  {
772  # if tag indicates privilege set
773  if (preg_match("/^[a-z]+Privileges\$/i",
774  $MethodName))
775  {
776  # save element for later processing
777  $PrivilegesToSet[$Field->Id()][$MethodName] = $Value;
778  }
779  else
780  {
781  # condense down any extraneous whitespace
782  $Value = preg_replace("/\s+/", " ", trim($Value));
783 
784  # set value for field
785  $Field->$MethodName($Value);
786  }
787  }
788  elseif ($MethodName == "VocabularyFile")
789  {
790  $VocabToLoad = $Value;
791  }
792  }
793 
794  # save the temp ID so that any privileges to set
795  # can be mapped to the actual ID when the field is
796  # made permanent
797  $TempId = $Field->Id();
798 
799  # make new field permanent
800  $Field->IsTempItem(FALSE);
801 
802  # load any vocabularies
803  if ($VocabToLoad !== NULL)
804  {
805  $Field->LoadVocabulary($VocabToLoad);
806  }
807 
808  # map privileges to set to the permanent field ID
809  if (isset($PrivilegesToSet) &&
810  isset($PrivilegesToSet[$TempId]) )
811  {
812  # copy the privileges over
813  $PrivilegesToSet[$Field->Id()] =
814  $PrivilegesToSet[$TempId];
815 
816  # remove the values for the temp ID
817  unset($PrivilegesToSet[$TempId]);
818  }
819  }
820  }
821  }
822 
823  # if we have schema-level privileges to set
824  if (count($XmlData->SchemaPrivileges))
825  {
826  foreach ($XmlData->SchemaPrivileges->children() as $PrivName => $PrivXml)
827  {
828  # if our current value for this privset is empty,
829  # take the one from the file
830  if ($this->$PrivName()->ComparisonCount() == 0)
831  {
832  # extract the values to set from the XML
833  $Value = $this->ConvertXmlToPrivilegeSet($PrivXml);
834  # set the privilege
835  $this->$PrivName($Value);
836  }
837  }
838  }
839 
840  # if we have privileges to set
841  if (isset($PrivilegesToSet))
842  {
843  # for each field with privileges
844  foreach ($PrivilegesToSet as $FieldId => $Privileges)
845  {
846  # load the field for which to set the privileges
847  $Field = new MetadataField($FieldId);
848 
849  # for each set of privileges for field
850  foreach ($Privileges as $MethodName => $Value)
851  {
852  # convert privilege value
853  $Value = $this->ConvertXmlToPrivilegeSet($Value);
854 
855  # if conversion failed
856  if ($Value === NULL)
857  {
858  # add resulting error messages to our list
859  $ErrorMsgs = $this->ErrorMessages(
860  "ConvertXmlToPrivilegeSet");
861  foreach ($ErrorMsgs as $Msg)
862  {
863  $this->ErrorMsgs[__METHOD__][] =
864  $Msg." (ConvertXmlToPrivilegeSet)";
865  }
866  }
867  else
868  {
869  # set value for field
870  $Field->$MethodName($Value);
871  }
872  }
873  }
874  }
875 
876  # if errors were found during creation
877  if (array_key_exists(__METHOD__, $this->ErrorMsgs) || $TestRun)
878  {
879  # remove any fields that were created
880  foreach ($this->NewFields as $Field)
881  {
882  $Field->Drop();
883  }
884  $this->NewFields = array();
885  }
886  else
887  {
888  # set owner for new fields (if supplied)
889  if ($Owner !== NULL)
890  {
891  foreach ($this->NewFields as $Field)
892  {
893  $Field->Owner($Owner);
894  }
895  }
896 
897  # if there were standard field mappings included
898  if (isset($XmlData->StandardFieldMapping))
899  {
900  # for each standard field mapping found
901  foreach ($XmlData->StandardFieldMapping as $MappingXml)
902  {
903  # if required values are supplied
904  if (isset($MappingXml->Name)
905  && isset($MappingXml->StandardName))
906  {
907  # get ID for specified field
908  $FieldName = (string)$MappingXml->Name;
909  $StandardName = (string)$MappingXml->StandardName;
910  $FieldId = $this->GetFieldIdByName($FieldName);
911 
912  # if field ID was found
913  if ($FieldId !== FALSE)
914  {
915  # set standard field mapping
916  $this->StdNameToFieldMapping(
917  $StandardName, $FieldId);
918  }
919  else
920  {
921  # log error about field not found
922  $this->ErrorMsgs[__METHOD__][] =
923  "Field not found with name '".$FieldName
924  ."' to map to standard field name '"
925  .$StandardName."'.";
926  }
927  }
928  else
929  {
930  # log error about missing value
931  if (!isset($MappingXml->Name))
932  {
933  $this->ErrorMsgs[__METHOD__][] =
934  "Field name missing for standard"
935  ." field mapping.";
936  }
937  if (!isset($MappingXml->StandardName))
938  {
939  $this->ErrorMsgs[__METHOD__][] =
940  "Standard field name missing for"
941  ." standard field mapping.";
942  }
943  }
944  }
945  }
946  }
947  }
948 
949  # report success or failure based on whether errors were recorded
950  return (array_key_exists(__METHOD__, $this->ErrorMsgs)) ? FALSE : TRUE;
951  }
952 
958  public function NewFields()
959  {
960  return $this->NewFields;
961  }
962 
972  public function ErrorMessages($Method = NULL)
973  {
974  if ($Method === NULL)
975  {
976  return $this->ErrorMsgs;
977  }
978  else
979  {
980  if (!method_exists($this, $Method))
981  {
982  throw new Exception("Error messages requested for non-existent"
983  ." method (".$Method.").");
984  }
985  return array_key_exists(__CLASS__."::".$Method, $this->ErrorMsgs)
986  ? $this->ErrorMsgs[__CLASS__."::".$Method] : array();
987  }
988  }
989 
999  public function AddFieldFromXml($Xml)
1000  {
1001  # assume field addition will fail
1002  $Field = self::MDFSTAT_ERROR;
1003 
1004  # add XML prefixes if needed
1005  $Xml = trim($Xml);
1006  if (!preg_match("/^<\?xml/i", $Xml))
1007  {
1008  if (!preg_match("/^<document>/i", $Xml))
1009  {
1010  $Xml = "<document>".$Xml."</document>";
1011  }
1012  $Xml = "<?xml version='1.0'?".">".$Xml;
1013  }
1014 
1015  # parse XML
1016  $XmlData = simplexml_load_string($Xml);
1017 
1018  # if required values are present
1019  if (is_object($XmlData)
1020  && isset($XmlData->Name)
1021  && isset($XmlData->Type)
1022  && constant("MetadataSchema::".$XmlData->Type))
1023  {
1024  # create the metadata field
1025  $Field = $this->AddField(
1026  $XmlData->Name,
1027  constant("MetadataSchema::".$XmlData->Type));
1028 
1029  # if field creation succeeded
1030  if ($Field != NULL)
1031  {
1032  # for other field attributes
1033  foreach ($XmlData as $MethodName => $Value)
1034  {
1035  # if they look valid and have not already been set
1036  if (method_exists($Field, $MethodName)
1037  && ($MethodName != "Name")
1038  && ($MethodName != "Type"))
1039  {
1040  # if tag indicates privilege set
1041  if (preg_match("/^[a-z]+Privileges\$/i",
1042  $MethodName))
1043  {
1044  # save element for later processing
1045  $PrivilegesToSet[$MethodName] = $Value;
1046  }
1047  else
1048  {
1049  # condense down any extraneous whitespace
1050  $Value = preg_replace("/\s+/", " ", trim($Value));
1051 
1052  # set value for field
1053  $Field->$MethodName($Value);
1054  }
1055  }
1056  }
1057 
1058  # make new field permanent
1059  $Field->IsTempItem(FALSE);
1060 
1061  # if we have privileges to set
1062  if (isset($PrivilegesToSet))
1063  {
1064  # for each set of privileges for field
1065  foreach ($PrivilegesToSet as $MethodName => $Value)
1066  {
1067  # convert privilege value
1068  $Value = $this->ConvertXmlToPrivilegeSet($Value);
1069 
1070  # if conversion failed
1071  if ($Value === NULL)
1072  {
1073  # add resulting error messages to our list
1074  $ErrorMsgs = $this->ErrorMessages(
1075  "ConvertXmlToPrivilegeSet");
1076  foreach ($ErrorMsgs as $Msg)
1077  {
1078  $this->ErrorMsgs[__METHOD__][] =
1079  $Msg." (ConvertXmlToPrivilegeSet)";
1080  }
1081  }
1082  else
1083  {
1084  # set value for field
1085  $Field->$MethodName($Value);
1086  }
1087  }
1088  }
1089  }
1090  }
1091 
1092  # return new field (if any) to caller
1093  return $Field;
1094  }
1095 
1101  public function DropField($FieldId)
1102  {
1103  $Field = $this->GetField($FieldId);
1104  if ($Field === NULL)
1105  {
1106  return FALSE;
1107  }
1108 
1109  # verify that this field is not mapped prior to dropping it
1110  foreach (self::$FieldMappings[$this->Id] as $Name => $FieldId)
1111  {
1112  if ($Field->Id() == $FieldId)
1113  {
1114  throw new Exception(
1115  "Attempt to delete ".$Field->Name()
1116  .", which is mapped as the standard ".$Name
1117  ." in the ".$this->Name()." Schema.");
1118  }
1119  }
1120 
1121  $GLOBALS["AF"]->SignalEvent("EVENT_PRE_FIELD_DELETE",
1122  array("FieldId" => $Field->Id()) );
1123 
1124  $Field->Drop();
1125 
1126  return TRUE;
1127  }
1128 
1134  public function GetField($FieldId)
1135  {
1136  # convert field name to ID if necessary
1137  if (!is_numeric($FieldId))
1138  {
1139  $FieldName = $FieldId;
1140  $FieldId = $this->GetFieldIdByName($FieldName);
1141  if ($FieldId === FALSE)
1142  {
1143  throw new InvalidArgumentException("Attempt to retrieve field"
1144  ." with unknown name (".$FieldName.").");
1145  }
1146  }
1147 
1148  # if caching is off or field is not already loaded
1149  if (!isset(self::$FieldCache[$FieldId]))
1150  {
1151  self::$FieldCache[$FieldId] = new MetadataField($FieldId);
1152  }
1153 
1154  # if field was from a different schema, bail
1155  if (self::$FieldCache[$FieldId]->SchemaId() != $this->Id())
1156  {
1157  throw new InvalidArgumentException(
1158  "Attempt to retrieve a field from a different schema");
1159  }
1160 
1161  return self::$FieldCache[$FieldId];
1162  }
1163 
1173  public function GetFieldByName($FieldName, $IgnoreCase = FALSE)
1174  {
1175  $FieldId = $this->GetFieldIdByName($FieldName, $IgnoreCase);
1176  return ($FieldId === FALSE) ? NULL : $this->GetField($FieldId);
1177  }
1178 
1186  public function GetFieldIdByName($FieldName, $IgnoreCase = FALSE)
1187  {
1188  return $this->GetItemIdByName($FieldName, $IgnoreCase);
1189  }
1190 
1196  public function FieldExists($Field)
1197  {
1198  return is_numeric($Field)
1199  ? $this->ItemExists($Field)
1200  : $this->NameIsInUse($Field);
1201  }
1202 
1216  public function GetFields($FieldTypes = NULL, $OrderType = NULL,
1217  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
1218  {
1219  # create empty array to pass back
1220  $Fields = array();
1221 
1222  # for each field type in database
1223  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields"
1224  ." WHERE SchemaId = ".intval($this->Id)
1225  .(!$IncludeDisabledFields ? " AND Enabled != 0" : "")
1226  .(!$IncludeTempFields ? " AND FieldId >= 0" : ""));
1227  while ($Record = $this->DB->FetchRow())
1228  {
1229  # if field type is known
1230  if (array_key_exists($Record["FieldType"], MetadataField::$FieldTypePHPEnums))
1231  {
1232  # if no specific type requested or if field is of requested type
1233  if (($FieldTypes == NULL)
1234  || (MetadataField::$FieldTypePHPEnums[$Record["FieldType"]]
1235  & $FieldTypes))
1236  {
1237  # create field object and add to array to be passed back
1238  $Fields[$Record["FieldId"]] = $this->GetField($Record["FieldId"]);
1239  }
1240  }
1241  }
1242 
1243  # if field sorting requested
1244  if ($OrderType !== NULL)
1245  {
1246  # update field comparison ordering if not set yet
1247  if (!$this->FieldCompareOrdersSet())
1248  {
1249  $this->UpdateFieldCompareOrders();
1250  }
1251 
1252  $this->FieldCompareType = $OrderType;
1253 
1254  # sort field array by requested order type
1255  uasort($Fields, array($this, "CompareFieldOrder"));
1256  }
1257 
1258  # return array of field objects to caller
1259  return $Fields;
1260  }
1261 
1276  public function GetFieldNames($FieldTypes = NULL, $OrderType = NULL,
1277  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
1278  {
1279  $Fields = $this->GetFields($FieldTypes, $OrderType,
1280  $IncludeDisabledFields, $IncludeTempFields);
1281 
1282  $FieldNames = array();
1283  foreach($Fields as $Field)
1284  {
1285  $FieldNames[$Field->Id()] = $Field->Name();
1286  }
1287 
1288  return $FieldNames;
1289  }
1290 
1309  public function GetFieldsAsOptionList($OptionListName, $FieldTypes = NULL,
1310  $SelectedFieldId = NULL, $IncludeNullOption = TRUE,
1311  $AddEntries = NULL, $AllowMultiple = FALSE, $Disabled = FALSE)
1312  {
1313  # retrieve requested fields
1314  $FieldNames = $this->GetFieldNames($FieldTypes);
1315 
1316  # transform field names to labels
1317  foreach ($FieldNames as $FieldId => $FieldName)
1318  {
1319  $FieldNames[$FieldId] = $this->GetField($FieldId)->GetDisplayName();
1320  }
1321 
1322  # add in null entry if requested
1323  if ($IncludeNullOption)
1324  {
1325  $FieldNames = array("" => "--") + $FieldNames;
1326  }
1327 
1328  # add additional entries if supplied
1329  if ($AddEntries)
1330  {
1331  $FieldNames = $FieldNames + $AddEntries;
1332  }
1333 
1334  # construct option list
1335  $OptList = new HtmlOptionList($OptionListName, $FieldNames, $SelectedFieldId);
1336  $OptList->MultipleAllowed($AllowMultiple);
1337  $OptList->Disabled($Disabled);
1338 
1339  # return option list HTML to caller
1340  return $OptList->GetHtml();
1341  }
1342 
1348  public function GetFieldTypes()
1349  {
1351  }
1352 
1358  public function GetAllowedFieldTypes()
1359  {
1361  }
1362 
1367  public function RemoveQualifierAssociations($QualifierIdOrObject)
1368  {
1369  # sanitize qualifier ID or grab it from object
1370  $QualifierIdOrObject = is_object($QualifierIdOrObject)
1371  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
1372 
1373  # delete intersection records from database
1374  $this->DB->Query("DELETE FROM FieldQualifierInts"
1375  ." WHERE QualifierId = ".$QualifierIdOrObject);
1376  }
1377 
1383  public function QualifierIsInUse($QualifierIdOrObject)
1384  {
1385  # sanitize qualifier ID or grab it from object
1386  $QualifierIdOrObject = is_object($QualifierIdOrObject)
1387  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
1388 
1389  # determine whether any fields use qualifier as default
1390  $DefaultCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount"
1391  ." FROM MetadataFields"
1392  ." WHERE DefaultQualifier = ".$QualifierIdOrObject,
1393  "RecordCount");
1394 
1395  # determine whether any fields are associated with qualifier
1396  $AssociationCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount"
1397  ." FROM FieldQualifierInts"
1398  ." WHERE QualifierId = ".$QualifierIdOrObject,
1399  "RecordCount");
1400 
1401  # report whether qualifier is in use based on defaults and associations
1402  return (($DefaultCount + $AssociationCount) > 0) ? TRUE : FALSE;
1403  }
1404 
1409  public function GetHighestFieldId()
1410  {
1411  return $this->GetHighestItemId();
1412  }
1413 
1422  public function StdNameToFieldMapping($MappedName, $FieldId = NULL)
1423  {
1424  if (func_num_args() > 1)
1425  {
1426  if (!isset(self::$FieldMappings[$this->Id][$MappedName])
1427  || (self::$FieldMappings[$this->Id][$MappedName] != $FieldId))
1428  {
1429  if (($FieldId !== NULL) && !$this->FieldExists($FieldId))
1430  {
1431  throw new InvalidArgumentException("Attempt to set"
1432  ." standard field mapping to invalid field ID"
1433  ." (".$FieldId.") at ".StdLib::GetMyCaller().".");
1434  }
1435 
1436  # if a mapping is set and is not NULL
1437  if (isset(self::$FieldMappings[$this->Id][$MappedName]))
1438  {
1439  $this->DB->Query("DELETE FROM StandardMetadataFieldMappings"
1440  ." WHERE SchemaId = '".addslashes($this->Id)
1441  ."' AND Name = '".addslashes($MappedName)."'");
1442  unset(self::$FieldMappings[$this->Id][$MappedName]);
1443  }
1444 
1445  if ($FieldId !== NULL)
1446  {
1447  $this->DB->Query("INSERT INTO StandardMetadataFieldMappings"
1448  ." (SchemaId, Name, FieldId) VALUES ('"
1449  .addslashes($this->Id)."', '".addslashes($MappedName)
1450  ."', '".addslashes($FieldId)."')");
1451  self::$FieldMappings[$this->Id][$MappedName] = $FieldId;
1452  }
1453  }
1454  }
1455  return isset(self::$FieldMappings[$this->Id][$MappedName])
1456  ? self::$FieldMappings[$this->Id][$MappedName] : NULL;
1457  }
1458 
1465  public function FieldToStdNameMapping($FieldId)
1466  {
1467  $MappedName = array_search($FieldId, self::$FieldMappings[$this->Id]);
1468  return ($MappedName === FALSE) ? NULL : $MappedName;
1469  }
1470 
1478  public function GetFieldByMappedName($MappedName)
1479  {
1480  return ($this->StdNameToFieldMapping($MappedName) == NULL) ? NULL
1481  : $this->GetField($this->StdNameToFieldMapping($MappedName));
1482  }
1483 
1491  public function GetFieldIdByMappedName($MappedName)
1492  {
1493  return $this->StdNameToFieldMapping($MappedName);
1494  }
1495 
1500  public function GetOwnedFields()
1501  {
1502  $Fields = array();
1503 
1504  $this->DB->Query("SELECT * FROM MetadataFields"
1505  ." WHERE Owner IS NOT NULL AND LENGTH(Owner) > 0"
1506  ." AND SchemaId = ".intval($this->Id));
1507 
1508  while (FALSE !== ($Row = $this->DB->FetchRow()))
1509  {
1510  $FieldId = $Row["FieldId"];
1511  $Fields[$FieldId] = $this->GetField($FieldId);
1512  }
1513 
1514  return $Fields;
1515  }
1516 
1522  public static function FieldExistsInAnySchema($Field)
1523  {
1524  # if we were given a field id, check to see if it exists
1525  self::LoadFieldNamesCache();
1526  if (is_numeric($Field) &&
1527  array_key_exists($Field, self::$FieldNamesCache))
1528  {
1529  return TRUE;
1530  }
1531 
1532  # otherwise, try to look up this field
1533  try
1534  {
1535  $FieldId = self::GetCanonicalFieldIdentifier($Field);
1536  return array_key_exists($FieldId, self::$FieldNamesCache) ?
1537  TRUE : FALSE;
1538  }
1539  catch (Exception $e)
1540  {
1541  # if we can't find the field, then it doesn't exist
1542  return FALSE;
1543  }
1544  }
1545 
1565  public static function GetCanonicalFieldIdentifier($Field, $SchemaId = NULL)
1566  {
1567  # check to make sure any specified schema is valid
1568  self::LoadFieldNamesCache();
1569  if ($SchemaId !== NULL)
1570  {
1571  if (!isset(self::$SchemaNamesCache[$SchemaId]))
1572  {
1573  throw new InvalidArgumentException(
1574  "Invalid schema ID supplied (".$SchemaId.").");
1575  }
1576  }
1577 
1578  # if field object was passed in
1579  if ($Field instanceof MetadataField)
1580  {
1581  # check to make sure field ID is within any specified schema
1582  if (($SchemaId !== NULL) && ($Field->SchemaId() != $SchemaId))
1583  {
1584  throw new Exception("Supplied field (".$Field
1585  .") is not within specified "
1586  .self::$SchemaNamesCache[$SchemaId]
1587  ." schema (".$SchemaId.")");
1588  }
1589 
1590  # return identifier from field to caller
1591  return $Field->Id();
1592  }
1593  # else if field ID was passed in
1594  elseif (is_numeric($Field))
1595  {
1596  # check to make sure field ID is valid
1597  if (!isset(self::$FieldNamesCache[$Field]))
1598  {
1599  throw new InvalidArgumentException(
1600  "Invalid field ID supplied (".$Field.").");
1601  }
1602 
1603  # check to make sure field ID is within any specified schema
1604  if (($SchemaId !== NULL)
1605  && (self::$FieldNamesCache[$Field]["SchemaId"] != $SchemaId))
1606  {
1607  throw new Exception("Supplied field ID (".$Field
1608  .") is not within specified "
1609  .self::$SchemaNamesCache[$SchemaId]
1610  ." schema (".$SchemaId.")");
1611  }
1612 
1613  # return supplied field ID to caller
1614  return (int)$Field;
1615  }
1616  # else if field name was passed in
1617  elseif (is_string($Field))
1618  {
1619  # look for field with specified name
1620  $FieldName = trim($Field);
1621  $FieldId = NULL;
1622  array_walk(self::$FieldNamesCache,
1623  function($Value, $Key, $FieldName) use (&$FieldId, $SchemaId)
1624  {
1625  if (($Value["QualifiedFieldName"] == $FieldName)
1626  && (($SchemaId === NULL)
1627  || ($Value["SchemaId"] == $SchemaId)))
1628  {
1629  $FieldId = $Key;
1630  }
1631  }, $FieldName);
1632 
1633  # if field with specified name not found
1634  if ($FieldId === NULL)
1635  {
1636  # log error and look for field with unqualified version of name
1637  # (NOTE: This is a temporary measure, to be removed once errors
1638  # are no longer regularly showing up in the log, in favor
1639  # of immediately throwing an exception if the name was
1640  # not found.)
1641  $FieldId = NULL;
1642  array_walk(self::$FieldNamesCache,
1643  function($Value, $Key, $FieldName) use (&$FieldId)
1644  {
1645  if ($Value["FieldName"] == $FieldName)
1646  {
1647  $FieldId = $Key;
1648  }
1649  }, $FieldName);
1650  if ($FieldId === NULL)
1651  {
1652  throw new Exception(
1653  "No field found with the name \"".$FieldName."\".");
1654  }
1655  else
1656  {
1657  $GLOBALS["AF"]->LogError(ApplicationFramework::LOGLVL_ERROR,
1658  "No field found with the name \"".$FieldName."\"."
1659  ." TRACE: ".StdLib::GetBacktraceAsString());
1660  }
1661  }
1662 
1663  # return found field ID to caller
1664  return $FieldId;
1665  }
1666  # else error out because we were given an illegal field argument
1667  else
1668  {
1669  throw new InvalidArgumentException(
1670  "Illegal field argument supplied.");
1671  }
1672  }
1673 
1688  public static function GetPrintableFieldName($Field)
1689  {
1690  # retrieve field ID
1691  $Id = self::GetCanonicalFieldIdentifier($Field);
1692 
1693  # if we have a label for this field, return it
1694  self::LoadFieldNamesCache();
1695  if (isset(self::$FieldNamesCache[$Id]))
1696  {
1697  $DisplayName = strlen(self::$FieldNamesCache[$Id]["FieldLabel"]) ?
1698  self::$FieldNamesCache[$Id]["FieldLabel"] :
1699  self::$FieldNamesCache[$Id]["FieldName"] ;
1700  return self::$FieldNamesCache[$Id]["SchemaPrefix"].$DisplayName;
1701  }
1702 
1703  # otherwise return a blank string
1704  return "";
1705  }
1706 
1711  public static function GetStandardFieldNames()
1712  {
1713  $DB = new Database();
1714  $DB->Query("SELECT DISTINCT Name FROM StandardMetadataFieldMappings");
1715  return $DB->FetchColumn("Name");
1716  }
1717 
1725  public static function TranslateLegacySearchValues(
1726  $FieldId, $Values)
1727  {
1728  # start out assuming we won't find any values to translate
1729  $ReturnValues = array();
1730 
1731  # try to grab the specified field
1732  try
1733  {
1734  $Field = new MetadataField($FieldId);
1735  }
1736  catch (Exception $e)
1737  {
1738  # field no longer exists, so there are no values to translate
1739  return $ReturnValues;
1740  }
1741 
1742  # if incoming value is not an array
1743  if (!is_array($Values))
1744  {
1745  # convert incoming value to an array
1746  $Values = array($Values);
1747  }
1748 
1749  # for each incoming value
1750  foreach ($Values as $Value)
1751  {
1752  # look up value for index
1753  if ($Field->Type() == self::MDFTYPE_FLAG)
1754  {
1755  # (for flag fields the value index (0 or 1) is used in Database)
1756  if ($Value >= 0)
1757  {
1758  $ReturnValues[] = "=".$Value;
1759  }
1760  }
1761  elseif ($Field->Type() == self::MDFTYPE_NUMBER)
1762  {
1763  # (for flag fields the value index (0 or 1) is used in Database)
1764  if ($Value >= 0)
1765  {
1766  $ReturnValues[] = ">=".$Value;
1767  }
1768  }
1769  elseif ($Field->Type() == self::MDFTYPE_USER)
1770  {
1771  $User = new CWUser(intval($Value));
1772  if ($User)
1773  {
1774  $ReturnValues[] = "=".$User->Get("UserName");
1775  }
1776  }
1777  elseif ($Field->Type() == self::MDFTYPE_OPTION)
1778  {
1779  if (!isset($PossibleFieldValues))
1780  {
1781  $PossibleFieldValues = $Field->GetPossibleValues();
1782  }
1783 
1784  if (isset($PossibleFieldValues[$Value]))
1785  {
1786  $ReturnValues[] = "=".$PossibleFieldValues[$Value];
1787  }
1788  }
1789  else
1790  {
1791  $NewValue = $Field->GetValueForId($Value);
1792  if ($NewValue !== NULL)
1793  {
1794  $ReturnValues[] = "=".$NewValue;
1795  }
1796  }
1797  }
1798 
1799  # return array of translated values to caller
1800  return $ReturnValues;
1801  }
1802 
1807  public static function GetAllSchemaIds()
1808  {
1809  return array_keys(self::GetAllSchemaNames());
1810  }
1811 
1816  public static function GetAllSchemaNames()
1817  {
1818  $DB = new Database();
1819  $DB->Query("SELECT SchemaId, Name FROM MetadataSchemas");
1820  return $DB->FetchColumn("Name", "SchemaId");
1821  }
1822 
1828  public static function GetAllSchemas()
1829  {
1830  # fetch IDs of all metadata schemas
1831  $SchemaIds = self::GetAllSchemaIds();
1832 
1833  # construct objects from the IDs
1834  $Schemas = array();
1835  foreach ($SchemaIds as $SchemaId)
1836  {
1837  $Schemas[$SchemaId] = new MetadataSchema($SchemaId);
1838  }
1839 
1840  # return schemas to caller
1841  return $Schemas;
1842  }
1843 
1850  public static function FieldUsedInPrivileges($FieldId)
1851  {
1852  # list of priv types we'll be checking
1853  $PrivTypes = array(
1854  "AuthoringPrivileges",
1855  "EditingPrivileges",
1856  "ViewingPrivileges");
1857 
1858  # iterate over each schema
1859  foreach (self::GetAllSchemas() as $Schema)
1860  {
1861  # see if the provided field is checked in any of the
1862  # schema-level privs, returning TRUE if so
1863  foreach ($PrivTypes as $PrivType)
1864  {
1865  if ($Schema->$PrivType()->ChecksField($FieldId))
1866  {
1867  return TRUE;
1868  }
1869  }
1870 
1871  # otherwise, iterate over all the field-level privs, returning true
1872  # if any of those check the provided field
1873  foreach ($Schema->GetFields() as $Field)
1874  {
1875  foreach ($PrivTypes as $PrivType)
1876  {
1877  if ($Field->$PrivType()->ChecksField($FieldId))
1878  {
1879  return TRUE;
1880  }
1881  }
1882  }
1883  }
1884 
1885  # nothing checks this field, return FALSE
1886  return FALSE;
1887  }
1888 
1894  public static function GetSchemaIdForName($Name)
1895  {
1896  $DB = new Database();
1897  $Id = $DB->Query("SELECT SchemaId FROM MetadataSchemas"
1898  ." WHERE Name = '".addslashes($Name)."'", "SchemaId");
1899  return ($Id === FALSE) ? NULL : $Id;
1900  }
1901 
1907  public static function SetOwnerListRetrievalFunction($Callback)
1908  {
1909  if (is_callable($Callback))
1910  {
1911  self::$OwnerListRetrievalFunction = $Callback;
1912  }
1913  }
1914 
1920  public static function NormalizeOwnedFields()
1921  {
1922  # if an owner list retrieval function and default schema exists
1923  if (self::$OwnerListRetrievalFunction
1924  && self::SchemaExistsWithId(self::SCHEMAID_DEFAULT))
1925  {
1926  # retrieve the list of owners that currently exist
1927  $OwnerList = call_user_func(self::$OwnerListRetrievalFunction);
1928 
1929  # an array is expected
1930  if (is_array($OwnerList))
1931  {
1932  $Schema = new MetadataSchema(self::SCHEMAID_DEFAULT);
1933 
1934  # get each metadata field that is owned by a plugin
1935  $OwnedFields = $Schema->GetOwnedFields();
1936 
1937  # loop through each owned field
1938  foreach ($OwnedFields as $OwnedField)
1939  {
1940  # the owner of the current field
1941  $Owner = $OwnedField->Owner();
1942 
1943  # if the owner of the field is in the list of owners that
1944  # currently exist, i.e., available plugins
1945  if (in_array($Owner, $OwnerList))
1946  {
1947  # enable the field and reset its "enable on owner return"
1948  # flag if the "enable on owner return" flag is currently
1949  # set to true. in other words, re-enable the field since
1950  # the owner has returned to the list of existing owners
1951  if ($OwnedField->EnableOnOwnerReturn())
1952  {
1953  $OwnedField->Enabled(TRUE);
1954  $OwnedField->EnableOnOwnerReturn(FALSE);
1955  }
1956  }
1957 
1958  # if the owner of the field is *not* in the list of owners
1959  # that currently exist, i.e., available plugins
1960  else
1961  {
1962  # first, see if the field is currently enabled since it
1963  # will determine whether the field is re-enabled when
1964  # the owner becomes available again
1965  $Enabled = $OwnedField->Enabled();
1966 
1967  # if the field is enabled, set its "enable on owner
1968  # return" flag to true and disable the field. nothing
1969  # needs to be done if the field is already disabled
1970  if ($Enabled)
1971  {
1972  $OwnedField->EnableOnOwnerReturn($Enabled);
1973  $OwnedField->Enabled(FALSE);
1974  }
1975  }
1976  }
1977  }
1978  }
1979  }
1980 
1985  protected function UpdateFieldCompareOrders()
1986  {
1987  $Index = 0;
1988 
1989  foreach ($this->GetDisplayOrder()->GetFields() as $Field)
1990  {
1991  $this->FieldCompareDisplayOrder[$Field->Id()] = $Index++;
1992  }
1993 
1994  $Index = 0;
1995 
1996  foreach ($this->GetEditOrder()->GetFields() as $Field)
1997  {
1998  $this->FieldCompareEditOrder[$Field->Id()] = $Index++;
1999  }
2000  }
2001 
2006  public function GetDisplayOrder()
2007  {
2008  return MetadataFieldOrder::GetOrderForSchema($this, self::ORDER_DISPLAY_NAME);
2009  }
2010 
2015  public function GetEditOrder()
2016  {
2017  return MetadataFieldOrder::GetOrderForSchema($this, self::ORDER_EDIT_NAME);
2018  }
2019 
2024  protected function FieldCompareOrdersSet()
2025  {
2026  return $this->FieldCompareDisplayOrder && $this->FieldCompareEditOrder;
2027  }
2028 
2036  protected function CompareFieldOrder($FieldA, $FieldB)
2037  {
2038  if ($this->FieldCompareType == self::MDFORDER_ALPHABETICAL)
2039  {
2040  return ($FieldA->GetDisplayName() < $FieldB->GetDisplayName()) ? -1 : 1;
2041  }
2042 
2043  if ($this->FieldCompareType == self::MDFORDER_EDITING)
2044  {
2046  }
2047 
2048  else
2049  {
2051  }
2052 
2053  $PositionA = GetArrayValue($Order, $FieldA->Id(), 0);
2054  $PositionB = GetArrayValue($Order, $FieldB->Id(), 0);
2055 
2056  return $PositionA < $PositionB ? -1 : 1;
2057  }
2058 
2059 
2063  public static function ClearStaticCaches()
2064  {
2065  self::$FieldCache = NULL;
2066  self::$FieldNamesCache = NULL;
2067  }
2068 
2069  # ---- PRIVATE INTERFACE -------------------------------------------------
2070 
2071  private $AuthoringPrivileges;
2072  private $EditingPrivileges;
2073  private $ErrorMsgs = array();
2074  private $FieldCompareType;
2075  private $Id;
2076  private $NewFields = array();
2077  private $ViewingPrivileges;
2078  private $ViewPage;
2079 
2080  private static $FieldMappings;
2081  private static $ValueCache = NULL;
2082 
2083  private static $FieldCache = NULL;
2084  private static $FieldNamesCache;
2085  private static $SchemaNamesCache;
2086 
2088 
2092  protected $FieldCompareDisplayOrder = array();
2093 
2097  protected $FieldCompareEditOrder = array();
2098 
2102  private static function LoadFieldNamesCache()
2103  {
2104  if (!isset(self::$FieldNamesCache))
2105  {
2106  self::$SchemaNamesCache = self::GetAllSchemaNames();
2107 
2108  $DB = new Database();
2109  $DB->Query("SELECT SchemaId, FieldId, FieldName, Label FROM MetadataFields"
2110  # (NOTE: This ordering is a temporary measure, to be removed when the
2111  # the error logging is removed from GetCanonicalFieldIdentifier().)
2112  ." ORDER BY SchemaId DESC");
2113  while ($Row = $DB->FetchRow())
2114  {
2115  $SchemaPrefix = ($Row["SchemaId"] == self::SCHEMAID_DEFAULT)
2116  ? "" : self::$SchemaNamesCache[$Row["SchemaId"]].": ";
2117 
2118  $TrimmedLabel = trim($Row["Label"]);
2119  $TrimmedName = trim($Row["FieldName"]);
2120 
2121  self::$FieldNamesCache[$Row["FieldId"]] = [
2122  "SchemaId" => $Row["SchemaId"],
2123  "SchemaPrefix" => $SchemaPrefix,
2124  "FieldName" => $TrimmedName,
2125  "QualifiedFieldName" => $SchemaPrefix.$TrimmedName,
2126  "FieldLabel" => $TrimmedLabel,
2127  ];
2128  }
2129  }
2130  }
2131 
2139  private function ConvertXmlToPrivilegeSet($Xml)
2140  {
2141  # clear any existing errors
2142  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
2143  { unset($this->ErrorMsgs[__METHOD__]); }
2144 
2145  # create new privilege set
2146  $PrivSet = new PrivilegeSet();
2147 
2148  # for each XML child
2149  foreach ($Xml as $Tag => $Value)
2150  {
2151  # take action based on element name
2152  switch ($Tag)
2153  {
2154  case "PrivilegeSet":
2155  # convert child data to new set
2156  $NewSet = $this->ConvertXmlToPrivilegeSet($Value);
2157 
2158  # add new set to our privilege set
2159  $PrivSet->AddSet($NewSet);
2160  break;
2161 
2162  case "AddCondition":
2163  # start with default values for optional parameters
2164  unset($ConditionField);
2165  $ConditionValue = NULL;
2166  $ConditionOperator = "==";
2167 
2168  # pull out parameters
2169  foreach ($Value as $ParamName => $ParamValue)
2170  {
2171  $ParamValue = trim($ParamValue);
2172  switch ($ParamName)
2173  {
2174  case "Field":
2175  $ConditionField = $this->GetField($ParamValue);
2176  if ($ConditionField === NULL)
2177  {
2178  # record error about unknown field
2179  $this->ErrorMsgs[__METHOD__][] =
2180  "Unknown metadata field name found"
2181  ." in AddCondition (".$ParamValue.").";
2182 
2183  # bail out
2184  return NULL;
2185  }
2186  break;
2187 
2188  case "Value":
2189  $ConditionValue = (string)$ParamValue;
2190 
2191  if ($ConditionValue == "NULL")
2192  {
2193  $ConditionValue = NULL;
2194  }
2195  elseif ($ConditionValue == "TRUE")
2196  {
2197  $ConditionValue = TRUE;
2198  }
2199  elseif ($ConditionValue == "FALSE")
2200  {
2201  $ConditionValue = FALSE;
2202  }
2203  break;
2204 
2205  case "Operator":
2206  $ConditionOperator = (string)$ParamValue;
2207  break;
2208 
2209  default:
2210  # record error about unknown parameter name
2211  $this->ErrorMsgs[__METHOD__][] =
2212  "Unknown tag found in AddCondition ("
2213  .$ParamName.").";
2214 
2215  # bail out
2216  return NULL;
2217  break;
2218  }
2219  }
2220 
2221  # if no field value
2222  if (!isset($ConditionField))
2223  {
2224  # record error about no field value
2225  $this->ErrorMsgs[__METHOD__][] =
2226  "No metadata field specified in AddCondition.";
2227 
2228  # bail out
2229  return NULL;
2230  }
2231 
2232  # if this is a vocabulary field
2233  $Factory = $ConditionField instanceof MetadataField ?
2234  $ConditionField->GetFactory() : NULL;
2235  if ($Factory !== NULL)
2236  {
2237  # look up the id of the provided value
2238  $ConditionValue = $Factory->GetItemIdByName(
2239  $ConditionValue);
2240 
2241  # if none was found, error out
2242  if ($ConditionValue === NULL)
2243  {
2244  $this->ErrorMsgs[__METHOD__][] =
2245  "Invalid value for field specified in AddCondition.";
2246  return NULL;
2247  }
2248  }
2249 
2250  # add conditional to privilege set
2251  $PrivSet->AddCondition($ConditionField,
2252  $ConditionValue, $ConditionOperator);
2253  break;
2254 
2255  default:
2256  # strip any excess whitespace off of value
2257  $Value = trim($Value);
2258 
2259  # if child looks like valid method name
2260  if (method_exists("PrivilegeSet", $Tag))
2261  {
2262  # convert constants if needed
2263  if (defined($Value))
2264  {
2265  $Value = constant($Value);
2266  }
2267  # convert booleans if needed
2268  elseif (strtoupper($Value) == "TRUE")
2269  {
2270  $Value = TRUE;
2271  }
2272  elseif (strtoupper($Value) == "FALSE")
2273  {
2274  $Value = FALSE;
2275  }
2276  # convert privilege flag names if needed and appropriate
2277  elseif (preg_match("/Privilege$/", $Tag))
2278  {
2279  static $Privileges;
2280  if (!isset($Privileges))
2281  {
2282  $PFactory = new PrivilegeFactory();
2283  $Privileges = $PFactory->GetPrivileges(TRUE, FALSE);
2284  }
2285  if (in_array($Value, $Privileges))
2286  {
2287  $Value = array_search($Value, $Privileges);
2288  }
2289  }
2290 
2291  # set value using child data
2292  $PrivSet->$Tag((string)$Value);
2293  }
2294  else
2295  {
2296  # record error about bad tag
2297  $this->ErrorMsgs[__METHOD__][] =
2298  "Unknown tag encountered (".$Tag.").";
2299 
2300  # bail out
2301  return NULL;
2302  }
2303  break;
2304  }
2305  }
2306 
2307  # return new privilege set to caller
2308  return $PrivSet;
2309  }
2310 
2318  protected function UpdateValue($ColumnName, $NewValue = DB_NOVALUE)
2319  {
2320  return $this->DB->UpdateValue("MetadataSchemas", $ColumnName, $NewValue,
2321  "SchemaId = ".intval($this->Id),
2322  self::$ValueCache[$this->Id]);
2323  }
2324 }
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:128
const DB_NOVALUE
Definition: Database.php:1738
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:198
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.