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 2011 Edward Almasy and Internet Scout
7 # http://scout.wisc.edu
8 #
9 
10 class MetadataSchema extends ItemFactory {
11 
12  # ---- PUBLIC INTERFACE --------------------------------------------------
13 
14  # types of field ordering
15  const MDFORDER_DISPLAY = 1;
16  const MDFORDER_EDITING = 2;
18 
19  # metadata field types
20  # (must parallel MetadataFields.FieldType declaration in install/CreateTables.sql
21  # and MetadataField::$FieldTypeDBEnums declaration below)
22  const MDFTYPE_TEXT = 1;
23  const MDFTYPE_PARAGRAPH = 2;
24  const MDFTYPE_NUMBER = 4;
25  const MDFTYPE_DATE = 8;
26  const MDFTYPE_TIMESTAMP = 16;
27  const MDFTYPE_FLAG = 32;
28  const MDFTYPE_TREE = 64;
30  const MDFTYPE_OPTION = 256;
31  const MDFTYPE_USER = 512;
32  const MDFTYPE_IMAGE = 1024;
33  const MDFTYPE_FILE = 2048;
34  const MDFTYPE_URL = 4096;
35  const MDFTYPE_POINT = 8192;
36 
37  # error status codes
38  const MDFSTAT_OK = 1;
39  const MDFSTAT_ERROR = 2;
43  const MDFSTAT_ILLEGALNAME = 32;
45  const MDFSTAT_ILLEGALLABEL = 128;
46 
50  public static $UseOldOrderingApi = FALSE;
51 
52  # object constructor
53  function MetadataSchema()
54  {
55  # set up item factory base class
56  $this->ItemFactory(
57  "MetadataField", "MetadataFields", "FieldId", "FieldName");
58 
59  # start with field info caching enabled
60  $this->CachingOn = TRUE;
61  }
62 
63  # turn internal caching of field info on or off
64  function CacheData($NewValue)
65  {
66  $this->CachingOn = $NewValue;
67  }
68 
69  # add new metadata field
70  function AddField($FieldName, $FieldType, $Optional = TRUE, $DefaultValue = NULL)
71  {
72  # create new field
73  $Field = new MetadataField(NULL, $FieldName, $FieldType, $Optional, $DefaultValue);
74 
75  # save error code if create failed and return NULL
76  if ($Field->Status() != MetadataSchema::MDFSTAT_OK)
77  {
78  $this->ErrorStatus = $Field->Status();
79  $Field = NULL;
80  }
81 
82  # return new field to caller
83  return $Field;
84  }
85 
95  function AddFieldFromXml($Xml)
96  {
97  # assume field addition will fail
98  $Field = self::MDFSTAT_ERROR;
99 
100  # add XML prefixes if needed
101  $Xml = trim($Xml);
102  if (!preg_match("/^<\?xml/i", $Xml))
103  {
104  if (!preg_match("/^<document>/i", $Xml))
105  {
106  $Xml = "<document>".$Xml."</document>";
107  }
108  $Xml = "<?xml version='1.0'?>".$Xml;
109  }
110 
111  # parse XML
112  $XmlData = simplexml_load_string($Xml);
113 
114  # if required values are present
115  if (is_object($XmlData)
116  && isset($XmlData->Name)
117  && isset($XmlData->Type)
118  && constant("MetadataSchema::".$XmlData->Type))
119  {
120  # create the metadata field
121  $Field = new MetadataField(NULL, $XmlData->Name,
122  constant("MetadataSchema::".$XmlData->Type));
123 
124  # if field creation failed
125  if ($Field->Status() !== self::MDFSTAT_OK)
126  {
127  # reset field value to error code
128  $Field = $Field->Status();
129  }
130  else
131  {
132  # for other field attributes
133  foreach ($XmlData as $MethodName => $Value)
134  {
135  # if they look valid and have not already been set
136  if (method_exists($Field, $MethodName)
137  && ($MethodName != "Name")
138  && ($MethodName != "Type"))
139  {
140  # condense down any extraneous whitespace
141  $Value = preg_replace("/\s+/", " ", trim($Value));
142 
143  # set value for field
144  $Field->$MethodName($Value);
145  }
146  }
147 
148  # make new field permanent
149  $Field->IsTempItem(FALSE);
150  }
151  }
152 
153  # return new field (if any) to caller
154  return $Field;
155  }
156 
157  # delete metadata field
158  function DropField($FieldId)
159  {
160  $Field = new MetadataField($FieldId);
161  $Field->Drop();
162  }
163 
164  # retrieve field by ID
165  function GetField($FieldId)
166  {
167  static $Fields;
168 
169  # if caching is off or field is already loaded
170  if (($this->CachingOn != TRUE) || !isset($Fields[$FieldId]))
171  {
172  # retrieve field
173  $Fields[$FieldId] = new MetadataField($FieldId);
174  }
175 
176  # return field to caller
177  return $Fields[$FieldId];
178  }
179 
186  function GetFieldByName($FieldName, $IgnoreCase = FALSE)
187  {
188  $FieldId = $this->GetFieldIdByName($FieldName, $IgnoreCase);
189  return ($FieldId === NULL) ? NULL : $this->GetField($FieldId);
190  }
191 
198  function GetFieldByLabel($FieldLabel, $IgnoreCase = FALSE)
199  {
200  $FieldId = $this->GetFieldIdByLabel($FieldLabel, $IgnoreCase);
201  return ($FieldId === NULL) ? NULL : $this->GetField($FieldId);
202  }
203 
211  function GetFieldIdByName($FieldName, $IgnoreCase = FALSE)
212  {
213  static $FieldIdsByName;
214 
215  # if caching is off or field ID is already loaded
216  if (($this->CachingOn != TRUE) || !isset($FieldIdsByName[$FieldName]))
217  {
218  # retrieve field ID from DB
219  $Condition = $IgnoreCase
220  ? "WHERE LOWER(FieldName) = '".addslashes(strtolower($FieldName))."'"
221  : "WHERE FieldName = '".addslashes($FieldName)."'";
222  $FieldIdsByName[$FieldName] = $this->DB->Query(
223  "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId");
224  }
225 
226  return $FieldIdsByName[$FieldName];
227  }
228 
236  function GetFieldIdByLabel($FieldLabel, $IgnoreCase = FALSE)
237  {
238  static $FieldIdsByLabel;
239 
240  # if caching is off or field ID is already loaded
241  if (($this->CachingOn != TRUE) || !isset($FieldIdsByLabel[$FieldLabel]))
242  {
243  # retrieve field ID from DB
244  $Condition = $IgnoreCase
245  ? "WHERE LOWER(Label) = '".addslashes(strtolower($FieldLabel))."'"
246  : "WHERE Label = '".addslashes($FieldLabel)."'";
247  $FieldIdsByLabel[$FieldLabel] = $this->DB->Query(
248  "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId");
249  }
250 
251  return $FieldIdsByLabel[$FieldLabel];
252  }
253 
254  # check whether field with specified name exists
255  function FieldExists($FieldName) { return $this->NameIsInUse($FieldName); }
256 
257  # retrieve array of fields
258  function GetFields($FieldTypes = NULL, $OrderType = NULL,
259  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
260  {
261  # create empty array to pass back
262  $Fields = array();
263 
264  # for each field type in database
265  if ($IncludeTempFields && $IncludeDisabledFields)
266  {
267  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields");
268  }
269  else
270  {
271  if ($IncludeTempFields)
272  {
273  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE Enabled != 0");
274  }
275  elseif ($IncludeDisabledFields)
276  {
277  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0");
278  }
279  else
280  {
281  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0 AND Enabled != 0");
282  }
283  }
284  while ($Record = $this->DB->FetchRow())
285  {
286  # if no specific type requested or if field is of requested type
287  if (($FieldTypes == NULL)
288  || (MetadataField::$FieldTypePHPEnums[$Record["FieldType"]] & $FieldTypes))
289  {
290  # create field object and add to array to be passed back
291  $Fields[$Record["FieldId"]] = $this->GetField($Record["FieldId"]);
292  }
293  }
294 
295  # if field sorting requested
296  if ($OrderType !== NULL)
297  {
298  # update field comparison ordering if not set yet
299  if (!self::FieldCompareOrdersSet())
300  {
301  self::UpdateFieldCompareOrders();
302  }
303 
304  $this->FieldCompareType = $OrderType;
305  $this->FieldOrderError = FALSE;
306 
307  # determine whether to use the old ordering API or not
308  $Callback = self::$UseOldOrderingApi
309  ? "CompareFieldOrderDeprecated" : "CompareFieldOrder";
310 
311  # sort field array by requested order type
312  uasort($Fields, array($this, $Callback));
313 
314  # if field order error detected
315  if ($this->FieldOrderError)
316  {
317  # repair (reset) field order
318  $OrderIndex = 1;
319  foreach ($Fields as $Field)
320  {
321  $Field->OrderPosition($OrderType, $OrderIndex);
322  $OrderIndex++;
323  }
324  }
325  }
326 
327  # return array of field objects to caller
328  return $Fields;
329  }
330 
331  function GetFieldNames($FieldTypes = NULL, $OrderType = NULL,
332  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
333  {
334  global $DB;
335 
336  $FieldNames=array();
337  $Fields = $this->GetFields($FieldTypes, $OrderType, $IncludeDisabledFields, $IncludeTempFields);
338 
339  foreach($Fields as $Field)
340  {
341  $DB->Query("SELECT FieldName FROM MetadataFields WHERE FieldId=".$Field->Id());
342  $FieldNames[ $Field->Id() ] = $DB->FetchField("FieldName");
343  }
344 
345  return $FieldNames;
346  }
347 
363  function GetFieldsAsOptionList($OptionListName, $FieldTypes = NULL,
364  $SelectedFieldId = NULL, $IncludeNullOption = TRUE,
365  $AddEntries = NULL, $AllowMultiple = FALSE)
366  {
367  # retrieve requested fields
368  $FieldNames = $this->GetFieldNames($FieldTypes);
369 
370  # transform field names to labels
371  foreach ($FieldNames as $FieldId => $FieldName)
372  {
373  $FieldNames[$FieldId] = $this->GetField($FieldId)->GetDisplayName();
374  }
375 
376  # begin HTML option list
377  $Html = "<select id=\"".$OptionListName."\" name=\"".$OptionListName."\"";
378 
379  # if multiple selections should be allowed
380  if ($AllowMultiple)
381  {
382  $Html .= " multiple=\"multiple\"";
383  }
384 
385  $Html .= ">\n";
386 
387  if ($IncludeNullOption)
388  {
389  $Html .= "<option value=\"\">--</option>\n";
390  }
391 
392  # make checking for IDs simpler
393  if (!is_array($SelectedFieldId))
394  {
395  $SelectedFieldId = array($SelectedFieldId);
396  }
397 
398  # for each metadata field
399  foreach ($FieldNames as $Id => $Name)
400  {
401  # add entry for field to option list
402  $Html .= "<option value=\"".$Id."\"";
403  if (in_array($Id, $SelectedFieldId)) { $Html .= " selected"; }
404  $Html .= ">".htmlspecialchars($Name)."</option>\n";
405  }
406 
407  # if additional entries were requested
408  if ($AddEntries)
409  {
410  foreach ($AddEntries as $Value => $Label)
411  {
412  $Html .= "<option value=\"".$Value."\"";
413  if (in_array($Value,$SelectedFieldId)) { $Html .= " selected"; }
414  $Html .= ">".htmlspecialchars($Label)."</option>\n";
415  }
416  }
417 
418  # end HTML option list
419  $Html .= "</select>\n";
420 
421  # return constructed HTML to caller
422  return $Html;
423  }
424 
425  # retrieve array of field types (enumerated type => field name)
426  function GetFieldTypes()
427  {
429  }
430 
431  # retrieve array of field types that user can create (enumerated type => field name)
433  {
435  }
436 
437  # remove all metadata field associations for a given qualifier
438  function RemoveQualifierAssociations($QualifierIdOrObject)
439  {
440  # sanitize qualifier ID or grab it from object
441  $QualifierIdOrObject = is_object($QualifierIdOrObject)
442  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
443 
444  # delete intersection records from database
445  $this->DB->Query("DELETE FROM FieldQualifierInts WHERE QualifierId = "
446  .$QualifierIdOrObject);
447  }
448 
449  # return whether qualifier is in use by metadata field
450  function QualifierIsInUse($QualifierIdOrObject)
451  {
452  # sanitize qualifier ID or grab it from object
453  $QualifierIdOrObject = is_object($QualifierIdOrObject)
454  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
455 
456  # determine whether any fields use qualifier as default
457  $DefaultCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM MetadataFields"
458  ." WHERE DefaultQualifier = ".$QualifierIdOrObject,
459  "RecordCount");
460 
461  # determine whether any fields are associated with qualifier
462  $AssociationCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM FieldQualifierInts"
463  ." WHERE QualifierId = ".$QualifierIdOrObject,
464  "RecordCount");
465 
466  # report whether qualifier is in use based on defaults and associations
467  return (($DefaultCount + $AssociationCount) > 0) ? TRUE : FALSE;
468  }
469 
470  # return highest field ID currently in use
471  function GetHighestFieldId() { return $this->GetHighestItemId(); }
472 
480  static function StdNameToFieldMapping($MappedName, $FieldId = NULL)
481  {
482  if ($FieldId !== NULL)
483  {
484  self::$FieldMappings[$MappedName] = $FieldId;
485  }
486  return isset(self::$FieldMappings[$MappedName])
487  ? self::$FieldMappings[$MappedName] : NULL;
488  }
489 
497  {
498  if ($FieldId != -1)
499  {
500  foreach (self::$FieldMappings as $MappedName => $MappedFieldId)
501  {
502  if ($MappedFieldId == $FieldId)
503  {
504  return $MappedName;
505  }
506  }
507  }
508  return NULL;
509  }
510 
518  function GetFieldByMappedName($MappedName)
519  {
520  return ($this->StdNameToFieldMapping($MappedName) == NULL) ? NULL
521  : $this->GetField($this->StdNameToFieldMapping($MappedName));
522  }
523 
528  public function GetOwnedFields()
529  {
530  $Fields = array();
531 
532  $this->DB->Query("
533  SELECT * FROM MetadataFields
534  WHERE Owner IS NOT NULL AND LENGTH(Owner) > 0");
535 
536  while (FALSE !== ($Row = $this->DB->FetchRow()))
537  {
538  $FieldId = $Row["FieldId"];
539  $Fields[$FieldId] = $this->GetField($FieldId);
540  }
541 
542  return $Fields;
543  }
544 
550  public static function SetOwnerListRetrievalFunction($Callback)
551  {
552  if (is_callable($Callback))
553  {
554  self::$OwnerListRetrievalFunction = $Callback;
555  }
556  }
557 
563  public static function NormalizeOwnedFields()
564  {
565  # if an owner list retrieval function exists
566  if (self::$OwnerListRetrievalFunction)
567  {
568  # retrieve the list of owners that currently exist
569  $OwnerList = call_user_func(self::$OwnerListRetrievalFunction);
570 
571  # an array is expected
572  if (is_array($OwnerList))
573  {
574  $Schema = new MetadataSchema();
575 
576  # get each metadata field that is owned by a plugin
577  $OwnedFields = $Schema->GetOwnedFields();
578 
579  # loop through each owned field
580  foreach ($OwnedFields as $OwnedField)
581  {
582  # the owner of the current field
583  $Owner = $OwnedField->Owner();
584 
585  # if the owner of the field is in the list of owners that
586  # currently exist, i.e., available plugins
587  if (in_array($Owner, $OwnerList))
588  {
589  # enable the field and reset its "enable on owner return"
590  # flag if the "enable on owner return" flag is currently
591  # set to true. in other words, re-enable the field since
592  # the owner has returned to the list of existing owners
593  if ($OwnedField->EnableOnOwnerReturn())
594  {
595  $OwnedField->Enabled(TRUE);
596  $OwnedField->EnableOnOwnerReturn(FALSE);
597  }
598  }
599 
600  # if the owner of the field is *not* in the list of owners
601  # that currently exist, i.e., available plugins
602  else
603  {
604  # first, see if the field is currently enabled since it
605  # will determine whether the field is re-enabled when
606  # the owner becomes available again
607  $Enabled = $OwnedField->Enabled();
608 
609  # if the field is enabled, set its "enable on owner
610  # return" flag to true and disable the field. nothing
611  # needs to be done if the field is already disabled
612  if ($Enabled)
613  {
614  $OwnedField->EnableOnOwnerReturn($Enabled);
615  $OwnedField->Enabled(FALSE);
616  }
617  }
618  }
619  }
620  }
621  }
622 
628  public static function UpdateFieldCompareOrders()
629  {
630  try
631  {
634 
635  $Index = 0;
636 
637  foreach ($DisplayOrder->GetFields() as $Field)
638  {
639  self::$FieldCompareDisplayOrder[$Field->Id()] = $Index++;
640  }
641 
642  $Index = 0;
643 
644  foreach ($EditOrder->GetFields() as $Field)
645  {
646  self::$FieldCompareEditOrder[$Field->Id()] = $Index++;
647  }
648  }
649 
650  catch (Exception $Exception)
651  {
652  # there was an error, so make no assumptions about the order
653  self::$FieldCompareDisplayOrder = array();
654  self::$FieldCompareEditOrder = array();
655  }
656  }
657 
662  protected static function FieldCompareOrdersSet()
663  {
664  return self::$FieldCompareDisplayOrder && self::$FieldCompareEditOrder;
665  }
666 
674  protected function CompareFieldOrder($FieldA, $FieldB)
675  {
676  if ($this->FieldCompareType == MetadataSchema::MDFORDER_ALPHABETICAL)
677  {
678  return ($FieldA->GetDisplayName() < $FieldB->GetDisplayName()) ? -1 : 1;
679  }
680 
681  if ($this->FieldCompareType == MetadataSchema::MDFORDER_EDITING)
682  {
683  $Order = self::$FieldCompareEditOrder;
684  }
685 
686  else
687  {
688  $Order = self::$FieldCompareDisplayOrder;
689  }
690 
691  $PositionA = GetArrayValue($Order, $FieldA->Id(), 0);
692  $PositionB = GetArrayValue($Order, $FieldB->Id(), 0);
693 
694  return $PositionA < $PositionB ? -1 : 1;
695  }
696 
697  # ---- PRIVATE INTERFACE -------------------------------------------------
698 
699  private $FieldCompareType;
700  private $CachingOn;
701  private static $FieldMappings;
702  protected static $OwnerListRetrievalFunction;
703 
707  protected static $FieldCompareDisplayOrder = array();
708 
712  protected static $FieldCompareEditOrder = array();
713 
714  # ---- DEPRECATED --------------------------------------------------------
715 
716  private $FieldOrderError;
717 
726  function MoveUpInOrder($FieldIdOrObj, $OrderType)
727  {
728  $this->MoveFieldInOrder($FieldIdOrObj, $OrderType, FALSE);
729  }
730 
739  function MoveDownInOrder($FieldIdOrObj, $OrderType)
740  {
741  $this->MoveFieldInOrder($FieldIdOrObj, $OrderType, TRUE);
742  }
743 
752  function CompareFieldOrderDeprecated($FieldA, $FieldB)
753  {
754  if ($this->FieldCompareType == MetadataSchema::MDFORDER_ALPHABETICAL)
755  {
756  return ($FieldA->GetDisplayName() < $FieldB->GetDisplayName()) ? -1 : 1;
757  }
758  else
759  {
760  if ($FieldA->OrderPosition($this->FieldCompareType)
761  == $FieldB->OrderPosition($this->FieldCompareType))
762  {
763  $this->FieldOrderError = TRUE;
764  return 0;
765  }
766  else
767  {
768  return ($FieldA->OrderPosition($this->FieldCompareType)
769  < $FieldB->OrderPosition($this->FieldCompareType)) ? -1 : 1;
770  }
771  }
772  }
773 
781  private function MoveFieldInOrder($FieldIdOrObj, $OrderType, $MoveFieldDown)
782  {
783  # grab field ID
784  $FieldId = is_object($FieldIdOrObj) ? $Field->Id() : $FieldIdOrObj;
785 
786  # retrieve array of fields
787  $Fields = $this->GetFields(NULL, $OrderType);
788 
789  # reverse array of fields if we are moving field down
790  if ($MoveFieldDown)
791  {
792  $Fields = array_reverse($Fields);
793  }
794 
795  # for each field in order
796  $PreviousField = NULL;
797  foreach ($Fields as $Field)
798  {
799  # if field is the field to be moved
800  if ($Field->Id() == $FieldId)
801  {
802  # if we have a previous field
803  if ($PreviousField !== NULL)
804  {
805  # swap field with previous field according to order type
806  $TempVal = $Field->OrderPosition($OrderType);
807  $Field->OrderPosition($OrderType, $PreviousField->OrderPosition($OrderType));
808  $PreviousField->OrderPosition($OrderType, $TempVal);
809  }
810  }
811 
812  # save field for next iteration
813  $PreviousField = $Field;
814  }
815  }
816 
817 }