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 StandardMetadataFieldMappings");
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  # delete fields associated with schema
275  $Fields = $this->GetFields(NULL, NULL, TRUE, TRUE);
276  foreach ($Fields as $FieldId => $Field)
277  {
278  $this->DropField($FieldId);
279  }
280 
281  # delete metadata field orders associated with schema
282  foreach (MetadataFieldOrder::GetOrdersForSchema($this) as $Order)
283  {
284  $Order->Delete();
285  }
286 
287  # remove schema info from database
288  $this->DB->Query("DELETE FROM MetadataSchemas WHERE SchemaId = "
289  .intval($this->Id));
290  }
291 
297  public static function SchemaExistsWithId($SchemaId)
298  {
299  if ($SchemaId === NULL) { return FALSE; }
300  $DB = new Database();
301  $DB->Query("SELECT * FROM MetadataSchemas"
302  ." WHERE SchemaId = ".intval($SchemaId));
303  return ($DB->NumRowsSelected() > 0) ? TRUE : FALSE;
304  }
305 
311  public function Id()
312  {
313  # return value to caller
314  return intval($this->Id);
315  }
316 
322  public function Name($NewValue = DB_NOVALUE)
323  {
324  return $this->UpdateValue("Name", $NewValue);
325  }
326 
334  public function AbbreviatedName($NewValue = DB_NOVALUE)
335  {
336  $AName = $this->UpdateValue("AbbreviatedName", $NewValue);
337  if (!strlen($AName))
338  {
339  $AName = strtoupper(substr($this->Name(), 0, 1));
340  }
341  return $AName;
342  }
343 
349  public function ResourceName($NewValue = DB_NOVALUE)
350  {
351  $RName = $this->UpdateValue("ResourceName", $NewValue);
352  if (!strlen($RName))
353  {
354  $RName = self::RESOURCENAME_DEFAULT;
355  }
356  return $RName;
357  }
358 
364  public function ViewPage($NewValue = DB_NOVALUE)
365  {
366  return $this->UpdateValue("ViewPage", $NewValue);
367  }
368 
374  public function AuthoringPrivileges(PrivilegeSet $NewValue = NULL)
375  {
376  # if new privileges supplied
377  if ($NewValue !== NULL)
378  {
379  # store new privileges in database
380  $this->UpdateValue("AuthoringPrivileges", $NewValue->Data());
381  $this->AuthoringPrivileges = $NewValue;
382  }
383 
384  # return current value to caller
385  return $this->AuthoringPrivileges;
386  }
387 
393  public function EditingPrivileges(PrivilegeSet $NewValue = NULL)
394  {
395  # if new privileges supplied
396  if ($NewValue !== NULL)
397  {
398  # store new privileges in database
399  $this->UpdateValue("EditingPrivileges", $NewValue->Data());
400  $this->EditingPrivileges = $NewValue;
401  }
402 
403  # return current value to caller
404  return $this->EditingPrivileges;
405  }
406 
412  public function ViewingPrivileges(PrivilegeSet $NewValue = NULL)
413  {
414  # if new privileges supplied
415  if ($NewValue !== NULL)
416  {
417  # store new privileges in database
418  $this->UpdateValue("ViewingPrivileges", $NewValue->Data());
419  $this->ViewingPrivileges = $NewValue;
420  }
421 
422  # return current value to caller
423  return $this->ViewingPrivileges;
424  }
425 
433  public function UserCanAuthor($User)
434  {
435  # get authoring privilege set for schema
436  $AuthorPrivs = $this->AuthoringPrivileges();
437 
438  # user can author if privileges are greater than resource set
439  $CanAuthor = $AuthorPrivs->MeetsRequirements($User);
440 
441  # allow plugins to modify result of permission check
442  $SignalResult = $GLOBALS["AF"]->SignalEvent(
443  "EVENT_RESOURCE_AUTHOR_PERMISSION_CHECK", array(
444  "Schema" => $this,
445  "User" => $User,
446  "CanAuthor" => $CanAuthor));
447  $CanAuthor = $SignalResult["CanAuthor"];
448 
449  # report back to caller whether user can author field
450  return $CanAuthor;
451  }
452 
460  public function UserCanEdit($User)
461  {
462  # get editing privilege set for schema
463  $EditPrivs = $this->EditingPrivileges();
464 
465  # user can edit if privileges are greater than resource set
466  $CanEdit = $EditPrivs->MeetsRequirements($User);
467 
468  # allow plugins to modify result of permission check
469  $SignalResult = $GLOBALS["AF"]->SignalEvent(
470  "EVENT_RESOURCE_EDIT_PERMISSION_CHECK", array(
471  "Resource" => NULL,
472  "User" => $User,
473  "CanEdit" => $CanEdit,
474  "Schema" => $this, ));
475  $CanEdit = $SignalResult["CanEdit"];
476 
477  # report back to caller whether user can edit field
478  return $CanEdit;
479  }
480 
488  public function UserCanView($User)
489  {
490  # get viewing privilege set for schema
491  $ViewPrivs = $this->ViewingPrivileges();
492 
493  # user can view if privileges are greater than resource set
494  $CanView = $ViewPrivs->MeetsRequirements($User);
495 
496  # allow plugins to modify result of permission check
497  $SignalResult = $GLOBALS["AF"]->SignalEvent(
498  "EVENT_RESOURCE_VIEW_PERMISSION_CHECK", array(
499  "Resource" => NULL,
500  "User" => $User,
501  "CanView" => $CanView,
502  "Schema" => $this, ));
503  $CanView = $SignalResult["CanView"];
504 
505  # report back to caller whether user can view field
506  return $CanView;
507  }
508 
514  public function GetViewPageIdParameter()
515  {
516  # get the query/GET parameters for the view page
517  $Query = parse_url($this->ViewPage(), PHP_URL_QUERY);
518 
519  # the URL couldn't be parsed
520  if (!is_string($Query))
521  {
522  return NULL;
523  }
524 
525  # parse the GET parameters out of the query string
526  $GetVars = ParseQueryString($Query);
527 
528  # search for the ID parameter
529  $Result = array_search("\$ID", $GetVars);
530 
531  return $Result !== FALSE ? $Result : NULL;
532  }
533 
546  public function PathMatchesViewPage($Path)
547  {
548  # get the query/GET parameters for the view page
549  $Query = parse_url($this->ViewPage(), PHP_URL_QUERY);
550 
551  # can't perform matching if the URL couldn't be parsed
552  if (!is_string($Query))
553  {
554  return FALSE;
555  }
556 
557  # parse the GET parameters out of the query string
558  $GetVars = ParseQueryString($Query);
559 
560  # now, get the query/GET parameters from the path given
561  $PathQuery = parse_url($Path, PHP_URL_QUERY);
562 
563  # can't perform matching if the URL couldn't be parsed
564  if (!is_string($PathQuery))
565  {
566  return FALSE;
567  }
568 
569  # parse the GET parameters out of the path's query string
570  $PathGetVars = ParseQueryString($PathQuery);
571 
572  # make sure the given path GET parameters contain at least the GET
573  # parameters from the view page and that all non-variable parameters are
574  # equal. the path GET parameters may contain more, which is okay
575  foreach ($GetVars as $GetVarName => $GetVarValue)
576  {
577  # there's a required parameter that is not included in the path GET
578  # parameters
579  if (!array_key_exists($GetVarName, $PathGetVars))
580  {
581  return FALSE;
582  }
583 
584  # require the path's value to be equal to the view page's value if
585  # the view page's value is not a variable,
586  if ($PathGetVars[$GetVarName] != $GetVarValue
587  && (!strlen($GetVarValue) || $GetVarValue{0} != "$"))
588  {
589  return FALSE;
590  }
591  }
592 
593  # the path matches the view page path
594  return TRUE;
595  }
596 
606  public function AddField(
607  $FieldName, $FieldType, $Optional = TRUE, $DefaultValue = NULL)
608  {
609  # clear any existing error messages
610  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
611  { unset($this->ErrorMsgs[__METHOD__]); }
612 
613  # create new field
614  try
615  {
616  $Field = MetadataField::Create($this->Id(), $FieldType,
617  $FieldName, $Optional, $DefaultValue);
618  }
619  catch (Exception $Exception)
620  {
621  $this->ErrorMsgs[__METHOD__][] = $Exception->getMessage();
622  $Field = NULL;
623  }
624 
625  # clear internal caches to make sure new field is recognized going forward
626  $this->ClearCaches();
627 
628  # return new field to caller
629  return $Field;
630  }
631 
646  public function AddFieldsFromXmlFile($FileName, $Owner = NULL, $TestRun = FALSE)
647  {
648  # clear loading status
649  $this->NewFields = array();
650  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
651  { unset($this->ErrorMsgs[__METHOD__]); }
652 
653  # check that file exists and is readable
654  if (!file_exists($FileName))
655  {
656  $this->ErrorMsgs[__METHOD__][] = "Could not find XML file '"
657  .$FileName."'.";
658  return FALSE;
659  }
660  elseif (!is_readable($FileName))
661  {
662  $this->ErrorMsgs[__METHOD__][] = "Could not read from XML file '"
663  .$FileName."'.";
664  return FALSE;
665  }
666 
667  # load XML from file
668  libxml_use_internal_errors(TRUE);
669  $XmlData = simplexml_load_file($FileName);
670  $Errors = libxml_get_errors();
671  libxml_use_internal_errors(FALSE);
672 
673  # if XML load failed
674  if ($XmlData === FALSE)
675  {
676  # retrieve XML error messages
677  foreach ($Errors as $Err)
678  {
679  $ErrType = ($Err->level == LIBXML_ERR_WARNING) ? "Warning"
680  : (($Err->level == LIBXML_ERR_WARNING) ? "Error"
681  : "Fatal Error");
682  $this->ErrorMsgs[__METHOD__][] = "XML ".$ErrType.": ".$Err->message
683  ." (".$Err->file.":".$Err->line.",".$Err->column.")";
684  }
685  }
686  # else if no metadata fields found record error message
687  elseif (!count($XmlData->MetadataField))
688  {
689  $this->ErrorMsgs[__METHOD__][] = "No metadata fields found.";
690  }
691  # else process metadata fields
692  else
693  {
694  # for each metadata field entry found
695  $FieldsAdded = 0;
696  $FieldIndex = 0;
697  foreach ($XmlData->MetadataField as $FieldXml)
698  {
699  $FieldIndex++;
700 
701  # pull out field type if present
702  if (isset($FieldXml->Type))
703  {
704  $FieldType = "MetadataSchema::".$FieldXml->Type;
705  if (!defined($FieldType))
706  {
707  $FieldType = "MetadataSchema::MDFTYPE_"
708  .strtoupper(preg_replace("/\\s+/", "",
709  $FieldXml->Type));
710  }
711  }
712 
713  # if required values are missing
714  if (!isset($FieldXml->Name) || !isset($FieldXml->Type)
715  || !defined($FieldType))
716  {
717  # add error message about required value missing
718  if (!isset($FieldXml->Name))
719  {
720  $this->ErrorMsgs[__METHOD__][] =
721  "Field name not found (MetadataField #"
722  .$FieldIndex.").";
723  }
724  if (!isset($FieldXml->Type) || !defined($FieldType))
725  {
726  $this->ErrorMsgs[__METHOD__][] =
727  "Valid type not found for field '"
728  .$FieldXml->Name."' (MetadataField #"
729  .$FieldIndex.").";
730  }
731  }
732  # else if there is not already a field with this name
733  elseif (!$this->NameIsInUse(trim($FieldXml->Name)))
734  {
735  # create new field
736  $Field = $this->AddField($FieldXml->Name, constant($FieldType));
737 
738  # if field creation failed
739  if ($Field === NULL)
740  {
741  # add any error message to our error list
742  $ErrorMsgs = $this->ErrorMessages("AddField");
743  foreach ($ErrorMsgs as $Msg)
744  {
745  $this->ErrorMsgs[__METHOD__][] =
746  $Msg." (AddField)";
747  }
748  }
749  else
750  {
751  # add field to list of created fields
752  $this->NewFields[$Field->Id()] = $Field;
753 
754  # assume no vocabulary to load
755  $VocabToLoad = NULL;
756 
757  # for other field attributes
758  foreach ($FieldXml as $MethodName => $Value)
759  {
760  # if tags look valid and have not already been set
761  if (method_exists($Field, $MethodName)
762  && ($MethodName != "Name")
763  && ($MethodName != "Type"))
764  {
765  # if tag indicates privilege set
766  if (preg_match("/^[a-z]+Privileges\$/i",
767  $MethodName))
768  {
769  # save element for later processing
770  $PrivilegesToSet[$Field->Id()][$MethodName] = $Value;
771  }
772  else
773  {
774  # condense down any extraneous whitespace
775  $Value = preg_replace("/\s+/", " ", trim($Value));
776 
777  # set value for field
778  $Field->$MethodName($Value);
779  }
780  }
781  elseif ($MethodName == "VocabularyFile")
782  {
783  $VocabToLoad = $Value;
784  }
785  }
786 
787  # save the temp ID so that any privileges to set
788  # can be mapped to the actual ID when the field is
789  # made permanent
790  $TempId = $Field->Id();
791 
792  # make new field permanent
793  $Field->IsTempItem(FALSE);
794 
795  # load any vocabularies
796  if ($VocabToLoad !== NULL)
797  {
798  $Field->LoadVocabulary($VocabToLoad);
799  }
800 
801  # map privileges to set to the permanent field ID
802  if (isset($PrivilegesToSet) &&
803  isset($PrivilegesToSet[$TempId]) )
804  {
805  # copy the privileges over
806  $PrivilegesToSet[$Field->Id()] =
807  $PrivilegesToSet[$TempId];
808 
809  # remove the values for the temp ID
810  unset($PrivilegesToSet[$TempId]);
811  }
812  }
813  }
814  }
815 
816  # if we have schema-level privileges to set
817  if (count($XmlData->SchemaPrivileges))
818  {
819  foreach ($XmlData->SchemaPrivileges->children() as $PrivName => $PrivXml)
820  {
821  # if our current value for this privset is empty,
822  # take the one from the file
823  if ($this->$PrivName()->ComparisonCount() == 0)
824  {
825  # extract the values to set from the XML
826  $Value = $this->ConvertXmlToPrivilegeSet($PrivXml);
827  # set the privilege
828  $this->$PrivName($Value);
829  }
830  }
831  }
832 
833  # if we have privileges to set
834  if (isset($PrivilegesToSet))
835  {
836  # for each field with privileges
837  foreach ($PrivilegesToSet as $FieldId => $Privileges)
838  {
839  # load the field for which to set the privileges
840  $Field = new MetadataField($FieldId);
841 
842  # for each set of privileges for field
843  foreach ($Privileges as $MethodName => $Value)
844  {
845  # convert privilege value
846  $Value = $this->ConvertXmlToPrivilegeSet($Value);
847 
848  # if conversion failed
849  if ($Value === NULL)
850  {
851  # add resulting error messages to our list
852  $ErrorMsgs = $this->ErrorMessages(
853  "ConvertXmlToPrivilegeSet");
854  foreach ($ErrorMsgs as $Msg)
855  {
856  $this->ErrorMsgs[__METHOD__][] =
857  $Msg." (ConvertXmlToPrivilegeSet)";
858  }
859  }
860  else
861  {
862  # set value for field
863  $Field->$MethodName($Value);
864  }
865  }
866  }
867  }
868 
869  # if errors were found during creation
870  if (array_key_exists(__METHOD__, $this->ErrorMsgs) || $TestRun)
871  {
872  # remove any fields that were created
873  foreach ($this->NewFields as $Field)
874  {
875  $Field->Drop();
876  }
877  $this->NewFields = array();
878  }
879  else
880  {
881  # set owner for new fields (if supplied)
882  if ($Owner !== NULL)
883  {
884  foreach ($this->NewFields as $Field)
885  {
886  $Field->Owner($Owner);
887  }
888  }
889 
890  # if there were standard field mappings included
891  if (isset($XmlData->StandardFieldMapping))
892  {
893  # for each standard field mapping found
894  foreach ($XmlData->StandardFieldMapping as $MappingXml)
895  {
896  # if required values are supplied
897  if (isset($MappingXml->Name)
898  && isset($MappingXml->StandardName))
899  {
900  # get ID for specified field
901  $FieldName = (string)$MappingXml->Name;
902  $StandardName = (string)$MappingXml->StandardName;
903  $FieldId = $this->GetFieldIdByName($FieldName);
904 
905  # if field ID was found
906  if ($FieldId !== FALSE)
907  {
908  # set standard field mapping
909  $this->StdNameToFieldMapping(
910  $StandardName, $FieldId);
911  }
912  else
913  {
914  # log error about field not found
915  $this->ErrorMsgs[__METHOD__][] =
916  "Field not found with name '".$FieldName
917  ."' to map to standard field name '"
918  .$StandardName."'.";
919  }
920  }
921  else
922  {
923  # log error about missing value
924  if (!isset($MappingXml->Name))
925  {
926  $this->ErrorMsgs[__METHOD__][] =
927  "Field name missing for standard"
928  ." field mapping.";
929  }
930  if (!isset($MappingXml->StandardName))
931  {
932  $this->ErrorMsgs[__METHOD__][] =
933  "Standard field name missing for"
934  ." standard field mapping.";
935  }
936  }
937  }
938  }
939  }
940  }
941 
942  # report success or failure based on whether errors were recorded
943  return (array_key_exists(__METHOD__, $this->ErrorMsgs)) ? FALSE : TRUE;
944  }
945 
951  public function NewFields()
952  {
953  return $this->NewFields;
954  }
955 
965  public function ErrorMessages($Method = NULL)
966  {
967  if ($Method === NULL)
968  {
969  return $this->ErrorMsgs;
970  }
971  else
972  {
973  if (!method_exists($this, $Method))
974  {
975  throw new Exception("Error messages requested for non-existent"
976  ." method (".$Method.").");
977  }
978  return array_key_exists(__CLASS__."::".$Method, $this->ErrorMsgs)
979  ? $this->ErrorMsgs[__CLASS__."::".$Method] : array();
980  }
981  }
982 
992  public function AddFieldFromXml($Xml)
993  {
994  # assume field addition will fail
995  $Field = self::MDFSTAT_ERROR;
996 
997  # add XML prefixes if needed
998  $Xml = trim($Xml);
999  if (!preg_match("/^<\?xml/i", $Xml))
1000  {
1001  if (!preg_match("/^<document>/i", $Xml))
1002  {
1003  $Xml = "<document>".$Xml."</document>";
1004  }
1005  $Xml = "<?xml version='1.0'?".">".$Xml;
1006  }
1007 
1008  # parse XML
1009  $XmlData = simplexml_load_string($Xml);
1010 
1011  # if required values are present
1012  if (is_object($XmlData)
1013  && isset($XmlData->Name)
1014  && isset($XmlData->Type)
1015  && constant("MetadataSchema::".$XmlData->Type))
1016  {
1017  # create the metadata field
1018  $Field = $this->AddField(
1019  $XmlData->Name,
1020  constant("MetadataSchema::".$XmlData->Type));
1021 
1022  # if field creation succeeded
1023  if ($Field != NULL)
1024  {
1025  # for other field attributes
1026  foreach ($XmlData as $MethodName => $Value)
1027  {
1028  # if they look valid and have not already been set
1029  if (method_exists($Field, $MethodName)
1030  && ($MethodName != "Name")
1031  && ($MethodName != "Type"))
1032  {
1033  # if tag indicates privilege set
1034  if (preg_match("/^[a-z]+Privileges\$/i",
1035  $MethodName))
1036  {
1037  # save element for later processing
1038  $PrivilegesToSet[$MethodName] = $Value;
1039  }
1040  else
1041  {
1042  # condense down any extraneous whitespace
1043  $Value = preg_replace("/\s+/", " ", trim($Value));
1044 
1045  # set value for field
1046  $Field->$MethodName($Value);
1047  }
1048  }
1049  }
1050 
1051  # make new field permanent
1052  $Field->IsTempItem(FALSE);
1053 
1054  # if we have privileges to set
1055  if (isset($PrivilegesToSet))
1056  {
1057  # for each set of privileges for field
1058  foreach ($PrivilegesToSet as $MethodName => $Value)
1059  {
1060  # convert privilege value
1061  $Value = $this->ConvertXmlToPrivilegeSet($Value);
1062 
1063  # if conversion failed
1064  if ($Value === NULL)
1065  {
1066  # add resulting error messages to our list
1067  $ErrorMsgs = $this->ErrorMessages(
1068  "ConvertXmlToPrivilegeSet");
1069  foreach ($ErrorMsgs as $Msg)
1070  {
1071  $this->ErrorMsgs[__METHOD__][] =
1072  $Msg." (ConvertXmlToPrivilegeSet)";
1073  }
1074  }
1075  else
1076  {
1077  # set value for field
1078  $Field->$MethodName($Value);
1079  }
1080  }
1081  }
1082  }
1083  }
1084 
1085  # return new field (if any) to caller
1086  return $Field;
1087  }
1088 
1094  public function DropField($FieldId)
1095  {
1096  $Field = $this->GetField($FieldId);
1097  if ($Field !== NULL)
1098  {
1099  global $AF;
1100  $AF->SignalEvent("EVENT_PRE_FIELD_DELETE",
1101  array("FieldId" => $Field->Id()) );
1102 
1103  $Field->Drop();
1104  return TRUE;
1105  }
1106  else
1107  {
1108  return FALSE;
1109  }
1110  }
1111 
1117  public function GetField($FieldId)
1118  {
1119  # convert field name to ID if necessary
1120  if (!is_numeric($FieldId))
1121  {
1122  $FieldName = $FieldId;
1123  $FieldId = $this->GetFieldIdByName($FieldName);
1124  if ($FieldId === FALSE)
1125  {
1126  throw new InvalidArgumentException("Attempt to retrieve field"
1127  ." with unknown name (".$FieldName.").");
1128  }
1129  }
1130 
1131  # if caching is off or field is not already loaded
1132  if (!isset(self::$FieldCache[$FieldId]))
1133  {
1134  self::$FieldCache[$FieldId] = new MetadataField($FieldId);
1135  }
1136 
1137  # if field was from a different schema, bail
1138  if (self::$FieldCache[$FieldId]->SchemaId() != $this->Id())
1139  {
1140  throw new Exception(
1141  "Attempt to retrieve a field from a different schema");
1142  }
1143 
1144  return self::$FieldCache[$FieldId];
1145  }
1146 
1156  public function GetFieldByName($FieldName, $IgnoreCase = FALSE)
1157  {
1158  $FieldId = $this->GetFieldIdByName($FieldName, $IgnoreCase);
1159  return ($FieldId === FALSE) ? NULL : $this->GetField($FieldId);
1160  }
1161 
1169  public function GetFieldIdByName($FieldName, $IgnoreCase = FALSE)
1170  {
1171  return $this->GetItemIdByName($FieldName, $IgnoreCase);
1172  }
1173 
1179  public function FieldExists($Field)
1180  {
1181  return is_numeric($Field)
1182  ? $this->ItemExists($Field)
1183  : $this->NameIsInUse($Field);
1184  }
1185 
1199  public function GetFields($FieldTypes = NULL, $OrderType = NULL,
1200  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
1201  {
1202  # create empty array to pass back
1203  $Fields = array();
1204 
1205  # for each field type in database
1206  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields"
1207  ." WHERE SchemaId = ".intval($this->Id)
1208  .(!$IncludeDisabledFields ? " AND Enabled != 0" : "")
1209  .(!$IncludeTempFields ? " AND FieldId >= 0" : ""));
1210  while ($Record = $this->DB->FetchRow())
1211  {
1212  # if field type is known
1213  if (array_key_exists($Record["FieldType"], MetadataField::$FieldTypePHPEnums))
1214  {
1215  # if no specific type requested or if field is of requested type
1216  if (($FieldTypes == NULL)
1217  || (MetadataField::$FieldTypePHPEnums[$Record["FieldType"]]
1218  & $FieldTypes))
1219  {
1220  # create field object and add to array to be passed back
1221  $Fields[$Record["FieldId"]] = $this->GetField($Record["FieldId"]);
1222  }
1223  }
1224  }
1225 
1226  # if field sorting requested
1227  if ($OrderType !== NULL)
1228  {
1229  # update field comparison ordering if not set yet
1230  if (!$this->FieldCompareOrdersSet())
1231  {
1232  $this->UpdateFieldCompareOrders();
1233  }
1234 
1235  $this->FieldCompareType = $OrderType;
1236 
1237  # sort field array by requested order type
1238  uasort($Fields, array($this, "CompareFieldOrder"));
1239  }
1240 
1241  # return array of field objects to caller
1242  return $Fields;
1243  }
1244 
1259  public function GetFieldNames($FieldTypes = NULL, $OrderType = NULL,
1260  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
1261  {
1262  $Fields = $this->GetFields($FieldTypes, $OrderType,
1263  $IncludeDisabledFields, $IncludeTempFields);
1264 
1265  $FieldNames = array();
1266  foreach($Fields as $Field)
1267  {
1268  $FieldNames[$Field->Id()] = $Field->Name();
1269  }
1270 
1271  return $FieldNames;
1272  }
1273 
1292  public function GetFieldsAsOptionList($OptionListName, $FieldTypes = NULL,
1293  $SelectedFieldId = NULL, $IncludeNullOption = TRUE,
1294  $AddEntries = NULL, $AllowMultiple = FALSE, $Disabled = FALSE)
1295  {
1296  # retrieve requested fields
1297  $FieldNames = $this->GetFieldNames($FieldTypes);
1298 
1299  # transform field names to labels
1300  foreach ($FieldNames as $FieldId => $FieldName)
1301  {
1302  $FieldNames[$FieldId] = $this->GetField($FieldId)->GetDisplayName();
1303  }
1304 
1305  # add in null entry if requested
1306  if ($IncludeNullOption)
1307  {
1308  $FieldNames = array("" => "--") + $FieldNames;
1309  }
1310 
1311  # add additional entries if supplied
1312  if ($AddEntries)
1313  {
1314  $FieldNames = $FieldNames + $AddEntries;
1315  }
1316 
1317  # construct option list
1318  $OptList = new HtmlOptionList($OptionListName, $FieldNames, $SelectedFieldId);
1319  $OptList->MultipleAllowed($AllowMultiple);
1320  $OptList->Disabled($Disabled);
1321 
1322  # return option list HTML to caller
1323  return $OptList->GetHtml();
1324  }
1325 
1331  public function GetFieldTypes()
1332  {
1334  }
1335 
1341  public function GetAllowedFieldTypes()
1342  {
1344  }
1345 
1350  public function RemoveQualifierAssociations($QualifierIdOrObject)
1351  {
1352  # sanitize qualifier ID or grab it from object
1353  $QualifierIdOrObject = is_object($QualifierIdOrObject)
1354  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
1355 
1356  # delete intersection records from database
1357  $this->DB->Query("DELETE FROM FieldQualifierInts"
1358  ." WHERE QualifierId = ".$QualifierIdOrObject);
1359  }
1360 
1366  public function QualifierIsInUse($QualifierIdOrObject)
1367  {
1368  # sanitize qualifier ID or grab it from object
1369  $QualifierIdOrObject = is_object($QualifierIdOrObject)
1370  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
1371 
1372  # determine whether any fields use qualifier as default
1373  $DefaultCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount"
1374  ." FROM MetadataFields"
1375  ." WHERE DefaultQualifier = ".$QualifierIdOrObject,
1376  "RecordCount");
1377 
1378  # determine whether any fields are associated with qualifier
1379  $AssociationCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount"
1380  ." FROM FieldQualifierInts"
1381  ." WHERE QualifierId = ".$QualifierIdOrObject,
1382  "RecordCount");
1383 
1384  # report whether qualifier is in use based on defaults and associations
1385  return (($DefaultCount + $AssociationCount) > 0) ? TRUE : FALSE;
1386  }
1387 
1392  public function GetHighestFieldId()
1393  {
1394  return $this->GetHighestItemId();
1395  }
1396 
1405  public function StdNameToFieldMapping($MappedName, $FieldId = NULL)
1406  {
1407  if (func_num_args() > 1)
1408  {
1409  if (!isset(self::$FieldMappings[$this->Id][$MappedName])
1410  || (self::$FieldMappings[$this->Id][$MappedName] != $FieldId))
1411  {
1412  if (($FieldId !== NULL) && !$this->FieldExists($FieldId))
1413  {
1414  throw new InvalidArgumentException("Attempt to set"
1415  ." standard field mapping to invalid field ID"
1416  ." (".$FieldId.") at ".StdLib::GetMyCaller().".");
1417  }
1418 
1419  # if a mapping is set and is not NULL
1420  if (isset(self::$FieldMappings[$this->Id][$MappedName]))
1421  {
1422  $this->DB->Query("DELETE FROM StandardMetadataFieldMappings"
1423  ." WHERE SchemaId = '".addslashes($this->Id)
1424  ."' AND Name = '".addslashes($MappedName)."'");
1425  unset(self::$FieldMappings[$this->Id][$MappedName]);
1426  }
1427 
1428  if ($FieldId !== NULL)
1429  {
1430  $this->DB->Query("INSERT INTO StandardMetadataFieldMappings"
1431  ." (SchemaId, Name, FieldId) VALUES ('"
1432  .addslashes($this->Id)."', '".addslashes($MappedName)
1433  ."', '".addslashes($FieldId)."')");
1434  self::$FieldMappings[$this->Id][$MappedName] = $FieldId;
1435  }
1436  }
1437  }
1438  return isset(self::$FieldMappings[$this->Id][$MappedName])
1439  ? self::$FieldMappings[$this->Id][$MappedName] : NULL;
1440  }
1441 
1448  public function FieldToStdNameMapping($FieldId)
1449  {
1450  $MappedName = array_search($FieldId, self::$FieldMappings[$this->Id]);
1451  return ($MappedName === FALSE) ? NULL : $MappedName;
1452  }
1453 
1461  public function GetFieldByMappedName($MappedName)
1462  {
1463  return ($this->StdNameToFieldMapping($MappedName) == NULL) ? NULL
1464  : $this->GetField($this->StdNameToFieldMapping($MappedName));
1465  }
1466 
1474  public function GetFieldIdByMappedName($MappedName)
1475  {
1476  return $this->StdNameToFieldMapping($MappedName);
1477  }
1478 
1483  public function GetOwnedFields()
1484  {
1485  $Fields = array();
1486 
1487  $this->DB->Query("SELECT * FROM MetadataFields"
1488  ." WHERE Owner IS NOT NULL AND LENGTH(Owner) > 0"
1489  ." AND SchemaId = ".intval($this->Id));
1490 
1491  while (FALSE !== ($Row = $this->DB->FetchRow()))
1492  {
1493  $FieldId = $Row["FieldId"];
1494  $Fields[$FieldId] = $this->GetField($FieldId);
1495  }
1496 
1497  return $Fields;
1498  }
1499 
1505  public static function FieldExistsInAnySchema($Field)
1506  {
1507  # first time through, set up our lookup array
1508  if (!isset(self::$FieldNamesCache))
1509  {
1510  self::LoadFieldNamesCache();
1511  }
1512 
1513  # if we were given a field id, check to see if it exists
1514  if (is_numeric($Field) &&
1515  array_key_exists($Field, self::$FieldNamesCache))
1516  {
1517  return TRUE;
1518  }
1519 
1520  # otherwise, try to look up this field
1521  try
1522  {
1523  $FieldId = self::GetCanonicalFieldIdentifier($Field);
1524  return array_key_exists($FieldId, $ValidFieldIds) ?
1525  TRUE : FALSE;
1526  }
1527  catch (Exception $e)
1528  {
1529  # if we can't find the field, then it doesn't exist
1530  return FALSE;
1531  }
1532  }
1533 
1549  public static function GetCanonicalFieldIdentifier($Field)
1550  {
1551  # if field object was passed in
1552  if ($Field instanceof MetadataField)
1553  {
1554  # return identifier from field to caller
1555  return $Field->Id();
1556  }
1557  # else if field ID was passed in
1558  elseif (is_numeric($Field))
1559  {
1560  # return supplied field ID to caller
1561  return (int)$Field;
1562  }
1563  # else if field name was passed in
1564  elseif (is_string($Field))
1565  {
1566  # retrieve field names if not already loaded
1567  if (!isset(self::$FieldNamesCache))
1568  {
1569  self::LoadFieldNamesCache();
1570  }
1571 
1572  # look for the specified name with a schema prefix
1573  $MatchingFields = array_filter(
1574  self::$FieldNamesCache, function($FieldInfo) use ($Field)
1575  {
1576  return ($FieldInfo["SchemaId"] != self::SCHEMAID_DEFAULT &&
1577  $FieldInfo["SchemaPrefix"].$FieldInfo["FieldName"] == $Field);
1578  });
1579  $Ids = array_keys($MatchingFields);
1580 
1581  # if we find a unique schema-prefixed name, use that
1582  # (note that more than one schema-prefixed name shouldn't
1583  # be possible because fields must be unique inside a schema)
1584  if (count($Ids) == 1)
1585  {
1586  return (int)$Ids[0];
1587  }
1588  elseif (count($Ids) > 1)
1589  {
1590  throw new Exception("Multiple schema-name prefixed "
1591  ."metadata fields found, which should be impossible.");
1592  }
1593 
1594  # look for field with specified name
1595  $MatchingFields = array_filter(
1596  self::$FieldNamesCache, function($FieldInfo) use ($Field)
1597  {
1598  return ($FieldInfo["FieldName"] == $Field);
1599  });
1600  $Ids = array_keys($MatchingFields);
1601 
1602  # if one matching field was found
1603  $FieldsFound = count($Ids);
1604  if ($FieldsFound == 1)
1605  {
1606  # return ID for matching field
1607  return (int)$Ids[0];
1608  }
1609  # else if multiple matching fields were found
1610  elseif ($FieldsFound > 1)
1611  {
1612  # return ID of field with the lowest schema ID
1613  $LowestSchemaId = PHP_INT_MAX;
1614  $ResultFieldId = NULL;
1615  foreach ($MatchingFields as $FieldId => $FieldInfo)
1616  {
1617  if ($FieldInfo["SchemaId"] < $LowestSchemaId)
1618  {
1619  $ResultFieldId = $FieldId;
1620  $LowestSchemaId = $FieldInfo["SchemaId"];
1621  }
1622  }
1623 
1624  if ($ResultFieldId === NULL)
1625  {
1626  throw new Exception(
1627  "Multiple fields called '".$Field."' found "
1628  ."but none of them have a SchemaId less than"
1629  ."PHP_INT_MAX, which should be impossible.");
1630  }
1631 
1632  return $ResultFieldId;
1633  }
1634  # else error out because no matching field found
1635  else
1636  {
1637  throw new Exception("No metadata field found with the"
1638  ." name \"".$Field."\".");
1639  }
1640  }
1641  # else error out because we were given an illegal field argument
1642  else
1643  {
1644  throw new InvalidArgumentException(
1645  "Illegal field argument supplied.");
1646  }
1647  }
1648 
1663  public static function GetPrintableFieldName($Field)
1664  {
1665  # retrieve field ID
1666  $Id = self::GetCanonicalFieldIdentifier($Field);
1667 
1668  # retrieve printable labels if not yet populated
1669  if (!isset(self::$FieldNamesCache))
1670  {
1671  self::LoadFieldNamesCache();
1672  }
1673 
1674  # if we have a label for this field, return it
1675  if (isset(self::$FieldNamesCache[$Id]))
1676  {
1677  $DisplayName = strlen(self::$FieldNamesCache[$Id]["FieldLabel"]) ?
1678  self::$FieldNamesCache[$Id]["FieldLabel"] :
1679  self::$FieldNamesCache[$Id]["FieldName"] ;
1680  return self::$FieldNamesCache[$Id]["SchemaPrefix"].$DisplayName;
1681  }
1682 
1683  # otherwise return a blank string
1684  return "";
1685  }
1686 
1694  public static function TranslateLegacySearchValues(
1695  $FieldId, $Values)
1696  {
1697  # start out assuming we won't find any values to translate
1698  $ReturnValues = array();
1699 
1700  # try to grab the specified field
1701  try
1702  {
1703  $Field = new MetadataField($FieldId);
1704  }
1705  catch (Exception $e)
1706  {
1707  # field no longer exists, so there are no values to translate
1708  return $ReturnValues;
1709  }
1710 
1711  # if incoming value is not an array
1712  if (!is_array($Values))
1713  {
1714  # convert incoming value to an array
1715  $Values = array($Values);
1716  }
1717 
1718  # for each incoming value
1719  foreach ($Values as $Value)
1720  {
1721  # look up value for index
1722  if ($Field->Type() == self::MDFTYPE_FLAG)
1723  {
1724  # (for flag fields the value index (0 or 1) is used in Database)
1725  if ($Value >= 0)
1726  {
1727  $ReturnValues[] = "=".$Value;
1728  }
1729  }
1730  elseif ($Field->Type() == self::MDFTYPE_NUMBER)
1731  {
1732  # (for flag fields the value index (0 or 1) is used in Database)
1733  if ($Value >= 0)
1734  {
1735  $ReturnValues[] = ">=".$Value;
1736  }
1737  }
1738  elseif ($Field->Type() == self::MDFTYPE_USER)
1739  {
1740  $User = new CWUser(intval($Value));
1741  if ($User)
1742  {
1743  $ReturnValues[] = "=".$User->Get("UserName");
1744  }
1745  }
1746  elseif ($Field->Type() == self::MDFTYPE_OPTION)
1747  {
1748  if (!isset($PossibleFieldValues))
1749  {
1750  $PossibleFieldValues = $Field->GetPossibleValues();
1751  }
1752 
1753  if (isset($PossibleFieldValues[$Value]))
1754  {
1755  $ReturnValues[] = "=".$PossibleFieldValues[$Value];
1756  }
1757  }
1758  else
1759  {
1760  $NewValue = $Field->GetValueForId($Value);
1761  if ($NewValue !== NULL)
1762  {
1763  $ReturnValues[] = "=".$NewValue;
1764  }
1765  }
1766  }
1767 
1768  # return array of translated values to caller
1769  return $ReturnValues;
1770  }
1771 
1776  public static function GetAllSchemaIds()
1777  {
1778  return array_keys(self::GetAllSchemaNames());
1779  }
1780 
1785  public static function GetAllSchemaNames()
1786  {
1787  $DB = new Database();
1788  $DB->Query("SELECT SchemaId, Name FROM MetadataSchemas");
1789  return $DB->FetchColumn("Name", "SchemaId");
1790  }
1791 
1797  public static function GetAllSchemas()
1798  {
1799  # fetch IDs of all metadata schemas
1800  $SchemaIds = self::GetAllSchemaIds();
1801 
1802  # construct objects from the IDs
1803  $Schemas = array();
1804  foreach ($SchemaIds as $SchemaId)
1805  {
1806  $Schemas[$SchemaId] = new MetadataSchema($SchemaId);
1807  }
1808 
1809  # return schemas to caller
1810  return $Schemas;
1811  }
1812 
1819  public static function FieldUsedInPrivileges($FieldId)
1820  {
1821  # list of priv types we'll be checking
1822  $PrivTypes = array(
1823  "AuthoringPrivileges",
1824  "EditingPrivileges",
1825  "ViewingPrivileges");
1826 
1827  # iterate over each schema
1828  foreach (self::GetAllSchemas() as $Schema)
1829  {
1830  # see if the provided field is checked in any of the
1831  # schema-level privs, returning TRUE if so
1832  foreach ($PrivTypes as $PrivType)
1833  {
1834  if ($Schema->$PrivType()->ChecksField($FieldId))
1835  {
1836  return TRUE;
1837  }
1838  }
1839 
1840  # otherwise, iterate over all the field-level privs, returning true
1841  # if any of those check the provided field
1842  foreach ($Schema->GetFields() as $Field)
1843  {
1844  foreach ($PrivTypes as $PrivType)
1845  {
1846  if ($Field->$PrivType()->ChecksField($FieldId))
1847  {
1848  return TRUE;
1849  }
1850  }
1851  }
1852  }
1853 
1854  # nothing checks this field, return FALSE
1855  return FALSE;
1856  }
1857 
1863  public static function GetSchemaIdForName($Name)
1864  {
1865  $DB = new Database();
1866  $Id = $DB->Query("SELECT SchemaId FROM MetadataSchemas"
1867  ." WHERE Name = '".addslashes($Name)."'", "SchemaId");
1868  return ($Id === FALSE) ? NULL : $Id;
1869  }
1870 
1876  public static function SetOwnerListRetrievalFunction($Callback)
1877  {
1878  if (is_callable($Callback))
1879  {
1880  self::$OwnerListRetrievalFunction = $Callback;
1881  }
1882  }
1883 
1889  public static function NormalizeOwnedFields()
1890  {
1891  # if an owner list retrieval function and default schema exists
1892  if (self::$OwnerListRetrievalFunction
1893  && self::SchemaExistsWithId(self::SCHEMAID_DEFAULT))
1894  {
1895  # retrieve the list of owners that currently exist
1896  $OwnerList = call_user_func(self::$OwnerListRetrievalFunction);
1897 
1898  # an array is expected
1899  if (is_array($OwnerList))
1900  {
1901  $Schema = new MetadataSchema(self::SCHEMAID_DEFAULT);
1902 
1903  # get each metadata field that is owned by a plugin
1904  $OwnedFields = $Schema->GetOwnedFields();
1905 
1906  # loop through each owned field
1907  foreach ($OwnedFields as $OwnedField)
1908  {
1909  # the owner of the current field
1910  $Owner = $OwnedField->Owner();
1911 
1912  # if the owner of the field is in the list of owners that
1913  # currently exist, i.e., available plugins
1914  if (in_array($Owner, $OwnerList))
1915  {
1916  # enable the field and reset its "enable on owner return"
1917  # flag if the "enable on owner return" flag is currently
1918  # set to true. in other words, re-enable the field since
1919  # the owner has returned to the list of existing owners
1920  if ($OwnedField->EnableOnOwnerReturn())
1921  {
1922  $OwnedField->Enabled(TRUE);
1923  $OwnedField->EnableOnOwnerReturn(FALSE);
1924  }
1925  }
1926 
1927  # if the owner of the field is *not* in the list of owners
1928  # that currently exist, i.e., available plugins
1929  else
1930  {
1931  # first, see if the field is currently enabled since it
1932  # will determine whether the field is re-enabled when
1933  # the owner becomes available again
1934  $Enabled = $OwnedField->Enabled();
1935 
1936  # if the field is enabled, set its "enable on owner
1937  # return" flag to true and disable the field. nothing
1938  # needs to be done if the field is already disabled
1939  if ($Enabled)
1940  {
1941  $OwnedField->EnableOnOwnerReturn($Enabled);
1942  $OwnedField->Enabled(FALSE);
1943  }
1944  }
1945  }
1946  }
1947  }
1948  }
1949 
1954  protected function UpdateFieldCompareOrders()
1955  {
1956  $Index = 0;
1957 
1958  foreach ($this->GetDisplayOrder()->GetFields() as $Field)
1959  {
1960  $this->FieldCompareDisplayOrder[$Field->Id()] = $Index++;
1961  }
1962 
1963  $Index = 0;
1964 
1965  foreach ($this->GetEditOrder()->GetFields() as $Field)
1966  {
1967  $this->FieldCompareEditOrder[$Field->Id()] = $Index++;
1968  }
1969  }
1970 
1975  public function GetDisplayOrder()
1976  {
1977  return MetadataFieldOrder::GetOrderForSchema($this, self::ORDER_DISPLAY_NAME);
1978  }
1979 
1984  public function GetEditOrder()
1985  {
1986  return MetadataFieldOrder::GetOrderForSchema($this, self::ORDER_EDIT_NAME);
1987  }
1988 
1993  protected function FieldCompareOrdersSet()
1994  {
1995  return $this->FieldCompareDisplayOrder && $this->FieldCompareEditOrder;
1996  }
1997 
2005  protected function CompareFieldOrder($FieldA, $FieldB)
2006  {
2007  if ($this->FieldCompareType == self::MDFORDER_ALPHABETICAL)
2008  {
2009  return ($FieldA->GetDisplayName() < $FieldB->GetDisplayName()) ? -1 : 1;
2010  }
2011 
2012  if ($this->FieldCompareType == self::MDFORDER_EDITING)
2013  {
2015  }
2016 
2017  else
2018  {
2020  }
2021 
2022  $PositionA = GetArrayValue($Order, $FieldA->Id(), 0);
2023  $PositionB = GetArrayValue($Order, $FieldB->Id(), 0);
2024 
2025  return $PositionA < $PositionB ? -1 : 1;
2026  }
2027 
2028 
2032  public static function ClearStaticCaches()
2033  {
2034  self::$FieldCache = NULL;
2035  self::$FieldNamesCache = NULL;
2036  }
2037 
2038  # ---- PRIVATE INTERFACE -------------------------------------------------
2039 
2040  private $AuthoringPrivileges;
2041  private $EditingPrivileges;
2042  private $ErrorMsgs = array();
2043  private $FieldCompareType;
2044  private $Id;
2045  private $NewFields = array();
2046  private $ViewingPrivileges;
2047  private $ViewPage;
2048 
2049  private static $FieldMappings;
2050  private static $ValueCache = NULL;
2051 
2052  private static $FieldCache = NULL;
2053  private static $FieldNamesCache = NULL;
2054 
2056 
2060  protected $FieldCompareDisplayOrder = array();
2061 
2065  protected $FieldCompareEditOrder = array();
2066 
2070  private static function LoadFieldNamesCache()
2071  {
2072  $SchemaNames = self::GetAllSchemaNames();
2073 
2074  $DB = new Database();
2075  $DB->Query("SELECT SchemaId, FieldId, FieldName, Label FROM MetadataFields");
2076  while ($Row = $DB->FetchRow())
2077  {
2078  $SchemaPrefix = ($Row["SchemaId"] != self::SCHEMAID_DEFAULT)
2079  ? $SchemaNames[$Row["SchemaId"]].": " : "";
2080 
2081  $TrimmedLabel = trim($Row["Label"]);
2082  $TrimmedName = trim($Row["FieldName"]);
2083 
2084  self::$FieldNamesCache[$Row["FieldId"]] = [
2085  "SchemaId" => $Row["SchemaId"],
2086  "SchemaPrefix" => $SchemaPrefix,
2087  "FieldName" => $TrimmedName,
2088  "FieldLabel" => $TrimmedLabel,
2089  ];
2090  }
2091  }
2092 
2100  private function ConvertXmlToPrivilegeSet($Xml)
2101  {
2102  # clear any existing errors
2103  if (array_key_exists(__METHOD__, $this->ErrorMsgs))
2104  { unset($this->ErrorMsgs[__METHOD__]); }
2105 
2106  # create new privilege set
2107  $PrivSet = new PrivilegeSet();
2108 
2109  # for each XML child
2110  foreach ($Xml as $Tag => $Value)
2111  {
2112  # take action based on element name
2113  switch ($Tag)
2114  {
2115  case "PrivilegeSet":
2116  # convert child data to new set
2117  $NewSet = $this->ConvertXmlToPrivilegeSet($Value);
2118 
2119  # add new set to our privilege set
2120  $PrivSet->AddSet($NewSet);
2121  break;
2122 
2123  case "AddCondition":
2124  # start with default values for optional parameters
2125  unset($ConditionField);
2126  $ConditionValue = NULL;
2127  $ConditionOperator = "==";
2128 
2129  # pull out parameters
2130  foreach ($Value as $ParamName => $ParamValue)
2131  {
2132  $ParamValue = trim($ParamValue);
2133  switch ($ParamName)
2134  {
2135  case "Field":
2136  $ConditionField = $this->GetField($ParamValue, TRUE);
2137  if ($ConditionField === NULL)
2138  {
2139  # record error about unknown field
2140  $this->ErrorMsgs[__METHOD__][] =
2141  "Unknown metadata field name found"
2142  ." in AddCondition (".$ParamValue.").";
2143 
2144  # bail out
2145  return NULL;
2146  }
2147  break;
2148 
2149  case "Value":
2150  $ConditionValue = (string)$ParamValue;
2151 
2152  if ($ConditionValue == "NULL")
2153  {
2154  $ConditionValue = NULL;
2155  }
2156  elseif ($ConditionValue == "TRUE")
2157  {
2158  $ConditionValue = TRUE;
2159  }
2160  elseif ($ConditionValue == "FALSE")
2161  {
2162  $ConditionValue = FALSE;
2163  }
2164  break;
2165 
2166  case "Operator":
2167  $ConditionOperator = (string)$ParamValue;
2168  break;
2169 
2170  default:
2171  # record error about unknown parameter name
2172  $this->ErrorMsgs[__METHOD__][] =
2173  "Unknown tag found in AddCondition ("
2174  .$ParamName.").";
2175 
2176  # bail out
2177  return NULL;
2178  break;
2179  }
2180  }
2181 
2182  # if no field value
2183  if (!isset($ConditionField))
2184  {
2185  # record error about no field value
2186  $this->ErrorMsgs[__METHOD__][] =
2187  "No metadata field specified in AddCondition.";
2188 
2189  # bail out
2190  return NULL;
2191  }
2192 
2193  # if this is a vocabulary field
2194  $Factory = $ConditionField instanceof MetadataField ?
2195  $ConditionField->GetFactory() : NULL;
2196  if ($Factory !== NULL)
2197  {
2198  # look up the id of the provided value
2199  $ConditionValue = $Factory->GetItemIdByName(
2200  $ConditionValue);
2201 
2202  # if none was found, error out
2203  if ($ConditionValue === NULL)
2204  {
2205  $this->ErrorMsgs[__METHOD__][] =
2206  "Invalid value for field specified in AddCondition.";
2207  return NULL;
2208  }
2209  }
2210 
2211  # add conditional to privilege set
2212  $PrivSet->AddCondition($ConditionField,
2213  $ConditionValue, $ConditionOperator);
2214  break;
2215 
2216  default:
2217  # strip any excess whitespace off of value
2218  $Value = trim($Value);
2219 
2220  # if child looks like valid method name
2221  if (method_exists("PrivilegeSet", $Tag))
2222  {
2223  # convert constants if needed
2224  if (defined($Value))
2225  {
2226  $Value = constant($Value);
2227  }
2228  # convert booleans if needed
2229  elseif (strtoupper($Value) == "TRUE")
2230  {
2231  $Value = TRUE;
2232  }
2233  elseif (strtoupper($Value) == "FALSE")
2234  {
2235  $Value = FALSE;
2236  }
2237  # convert privilege flag names if needed and appropriate
2238  elseif (preg_match("/Privilege$/", $Tag))
2239  {
2240  static $Privileges;
2241  if (!isset($Privileges))
2242  {
2243  $PFactory = new PrivilegeFactory();
2244  $Privileges = $PFactory->GetPrivileges(TRUE, FALSE);
2245  }
2246  if (in_array($Value, $Privileges))
2247  {
2248  $Value = array_search($Value, $Privileges);
2249  }
2250  }
2251 
2252  # set value using child data
2253  $PrivSet->$Tag((string)$Value);
2254  }
2255  else
2256  {
2257  # record error about bad tag
2258  $this->ErrorMsgs[__METHOD__][] =
2259  "Unknown tag encountered (".$Tag.").";
2260 
2261  # bail out
2262  return NULL;
2263  }
2264  break;
2265  }
2266  }
2267 
2268  # return new privilege set to caller
2269  return $PrivSet;
2270  }
2271 
2279  protected function UpdateValue($ColumnName, $NewValue = DB_NOVALUE)
2280  {
2281  return $this->DB->UpdateValue("MetadataSchemas", $ColumnName, $NewValue,
2282  "SchemaId = ".intval($this->Id),
2283  self::$ValueCache[$this->Id]);
2284  }
2285 }
const MDFSTAT_ILLEGALLABEL
const ORDER_DISPLAY_NAME
GetHighestItemId($IgnoreSqlCondition=FALSE)
Retrieve highest item ID in use.
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.
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:23
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 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.
static GetCanonicalFieldIdentifier($Field)
Retrieve canonical identifier for field.
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
const DB_NOVALUE
Definition: Database.php:1675
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:141
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.