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 
47  public static $UseOldOrderingApi = FALSE;
48 
49  # object constructor
50  function MetadataSchema()
51  {
52  # set up item factory base class
53  $this->ItemFactory(
54  "MetadataField", "MetadataFields", "FieldId", "FieldName");
55 
56  # start with field info caching enabled
57  $this->CachingOn = TRUE;
58  }
59 
60  # turn internal caching of field info on or off
61  function CacheData($NewValue)
62  {
63  $this->CachingOn = $NewValue;
64  }
65 
66  # add new metadata field
67  function AddField($FieldName, $FieldType, $Optional = TRUE, $DefaultValue = NULL)
68  {
69  # create new field
70  $Field = new MetadataField(NULL, $FieldName, $FieldType, $Optional, $DefaultValue);
71 
72  # save error code if create failed and return NULL
73  if ($Field->Status() != MetadataSchema::MDFSTAT_OK)
74  {
75  $this->ErrorStatus = $Field->Status();
76  $Field = NULL;
77  }
78 
79  # return new field to caller
80  return $Field;
81  }
82 
92  function AddFieldFromXml($Xml)
93  {
94  # assume field addition will fail
95  $Field = self::MDFSTAT_ERROR;
96 
97  # add XML prefixes if needed
98  $Xml = trim($Xml);
99  if (!preg_match("/^<\?xml/i", $Xml))
100  {
101  if (!preg_match("/^<document>/i", $Xml))
102  {
103  $Xml = "<document>".$Xml."</document>";
104  }
105  $Xml = "<?xml version='1.0'?>".$Xml;
106  }
107 
108  # parse XML
109  $XmlData = simplexml_load_string($Xml);
110 
111  # if required values are present
112  if (is_object($XmlData)
113  && isset($XmlData->Name)
114  && isset($XmlData->Type)
115  && constant("MetadataSchema::".$XmlData->Type))
116  {
117  # create the metadata field
118  $Field = new MetadataField(NULL, $XmlData->Name,
119  constant("MetadataSchema::".$XmlData->Type));
120 
121  # if field creation failed
122  if ($Field->Status() !== self::MDFSTAT_OK)
123  {
124  # reset field value to error code
125  $Field = $Field->Status();
126  }
127  else
128  {
129  # for other field attributes
130  foreach ($XmlData as $MethodName => $Value)
131  {
132  # if they look valid and have not already been set
133  if (method_exists($Field, $MethodName)
134  && ($MethodName != "Name")
135  && ($MethodName != "Type"))
136  {
137  # condense down any extraneous whitespace
138  $Value = preg_replace("/\s+/", " ", trim($Value));
139 
140  # set value for field
141  $Field->$MethodName($Value);
142  }
143  }
144 
145  # make new field permanent
146  $Field->IsTempItem(FALSE);
147  }
148  }
149 
150  # return new field (if any) to caller
151  return $Field;
152  }
153 
154  # delete metadata field
155  function DropField($FieldId)
156  {
157  $Field = new MetadataField($FieldId);
158  $Field->Drop();
159  }
160 
161  # retrieve field by ID
162  function GetField($FieldId)
163  {
164  static $Fields;
165 
166  # if caching is off or field is already loaded
167  if (($this->CachingOn != TRUE) || !isset($Fields[$FieldId]))
168  {
169  # retrieve field
170  $Fields[$FieldId] = new MetadataField($FieldId);
171  }
172 
173  # return field to caller
174  return $Fields[$FieldId];
175  }
176 
183  function GetFieldByName($FieldName, $IgnoreCase = FALSE)
184  {
185  $FieldId = $this->GetFieldIdByName($FieldName, $IgnoreCase);
186  return ($FieldId === NULL) ? NULL : $this->GetField($FieldId);
187  }
188 
195  function GetFieldByLabel($FieldLabel, $IgnoreCase = FALSE)
196  {
197  $FieldId = $this->GetFieldIdByLabel($FieldLabel, $IgnoreCase);
198  return ($FieldId === NULL) ? NULL : $this->GetField($FieldId);
199  }
200 
208  function GetFieldIdByName($FieldName, $IgnoreCase = FALSE)
209  {
210  static $FieldIdsByName;
211 
212  # if caching is off or field ID is already loaded
213  if (($this->CachingOn != TRUE) || !isset($FieldIdsByName[$FieldName]))
214  {
215  # retrieve field ID from DB
216  $Condition = $IgnoreCase
217  ? "WHERE LOWER(FieldName) = '".addslashes(strtolower($FieldName))."'"
218  : "WHERE FieldName = '".addslashes($FieldName)."'";
219  $FieldIdsByName[$FieldName] = $this->DB->Query(
220  "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId");
221  }
222 
223  return $FieldIdsByName[$FieldName];
224  }
225 
233  function GetFieldIdByLabel($FieldLabel, $IgnoreCase = FALSE)
234  {
235  static $FieldIdsByLabel;
236 
237  # if caching is off or field ID is already loaded
238  if (($this->CachingOn != TRUE) || !isset($FieldIdsByLabel[$FieldLabel]))
239  {
240  # retrieve field ID from DB
241  $Condition = $IgnoreCase
242  ? "WHERE LOWER(Label) = '".addslashes(strtolower($FieldLabel))."'"
243  : "WHERE Label = '".addslashes($FieldLabel)."'";
244  $FieldIdsByLabel[$FieldLabel] = $this->DB->Query(
245  "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId");
246  }
247 
248  return $FieldIdsByLabel[$FieldLabel];
249  }
250 
251  # check whether field with specified name exists
252  function FieldExists($FieldName) { return $this->NameIsInUse($FieldName); }
253 
254  # retrieve array of fields
255  function GetFields($FieldTypes = NULL, $OrderType = NULL,
256  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
257  {
258  # create empty array to pass back
259  $Fields = array();
260 
261  # for each field type in database
262  if ($IncludeTempFields && $IncludeDisabledFields)
263  {
264  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields");
265  }
266  else
267  {
268  if ($IncludeTempFields)
269  {
270  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE Enabled != 0");
271  }
272  elseif ($IncludeDisabledFields)
273  {
274  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0");
275  }
276  else
277  {
278  $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0 AND Enabled != 0");
279  }
280  }
281  while ($Record = $this->DB->FetchRow())
282  {
283  # if no specific type requested or if field is of requested type
284  if (($FieldTypes == NULL)
285  || (MetadataField::$FieldTypePHPEnums[$Record["FieldType"]] & $FieldTypes))
286  {
287  # create field object and add to array to be passed back
288  $Fields[$Record["FieldId"]] = $this->GetField($Record["FieldId"]);
289  }
290  }
291 
292  # if field sorting requested
293  if ($OrderType !== NULL)
294  {
295  # update field comparison ordering if not set yet
296  if (!self::FieldCompareOrdersSet())
297  {
298  self::UpdateFieldCompareOrders();
299  }
300 
301  $this->FieldCompareType = $OrderType;
302 
303  # sort field array by requested order type
304  uasort($Fields, array($this, "CompareFieldOrder"));
305  }
306 
307  # return array of field objects to caller
308  return $Fields;
309  }
310 
311  function GetFieldNames($FieldTypes = NULL, $OrderType = NULL,
312  $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
313  {
314  global $DB;
315 
316  $FieldNames=array();
317  $Fields = $this->GetFields($FieldTypes, $OrderType, $IncludeDisabledFields, $IncludeTempFields);
318 
319  foreach($Fields as $Field)
320  {
321  $DB->Query("SELECT FieldName FROM MetadataFields WHERE FieldId=".$Field->Id());
322  $FieldNames[ $Field->Id() ] = $DB->FetchField("FieldName");
323  }
324 
325  return $FieldNames;
326  }
327 
343  function GetFieldsAsOptionList($OptionListName, $FieldTypes = NULL,
344  $SelectedFieldId = NULL, $IncludeNullOption = TRUE,
345  $AddEntries = NULL, $AllowMultiple = FALSE)
346  {
347  # retrieve requested fields
348  $FieldNames = $this->GetFieldNames($FieldTypes);
349 
350  # transform field names to labels
351  foreach ($FieldNames as $FieldId => $FieldName)
352  {
353  $FieldNames[$FieldId] = $this->GetField($FieldId)->GetDisplayName();
354  }
355 
356  # begin HTML option list
357  $Html = "<select id=\"".$OptionListName."\" name=\"".$OptionListName."\"";
358 
359  # if multiple selections should be allowed
360  if ($AllowMultiple)
361  {
362  $Html .= " multiple=\"multiple\"";
363  }
364 
365  $Html .= ">\n";
366 
367  if ($IncludeNullOption)
368  {
369  $Html .= "<option value=\"\">--</option>\n";
370  }
371 
372  # make checking for IDs simpler
373  if (!is_array($SelectedFieldId))
374  {
375  $SelectedFieldId = array($SelectedFieldId);
376  }
377 
378  # for each metadata field
379  foreach ($FieldNames as $Id => $Name)
380  {
381  # add entry for field to option list
382  $Html .= "<option value=\"".$Id."\"";
383  if (in_array($Id, $SelectedFieldId)) { $Html .= " selected"; }
384  $Html .= ">".htmlspecialchars($Name)."</option>\n";
385  }
386 
387  # if additional entries were requested
388  if ($AddEntries)
389  {
390  foreach ($AddEntries as $Value => $Label)
391  {
392  $Html .= "<option value=\"".$Value."\"";
393  if (in_array($Value,$SelectedFieldId)) { $Html .= " selected"; }
394  $Html .= ">".htmlspecialchars($Label)."</option>\n";
395  }
396  }
397 
398  # end HTML option list
399  $Html .= "</select>\n";
400 
401  # return constructed HTML to caller
402  return $Html;
403  }
404 
405  # retrieve array of field types (enumerated type => field name)
406  function GetFieldTypes()
407  {
409  }
410 
411  # retrieve array of field types that user can create (enumerated type => field name)
413  {
415  }
416 
417  # remove all metadata field associations for a given qualifier
418  function RemoveQualifierAssociations($QualifierIdOrObject)
419  {
420  # sanitize qualifier ID or grab it from object
421  $QualifierIdOrObject = is_object($QualifierIdOrObject)
422  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
423 
424  # delete intersection records from database
425  $this->DB->Query("DELETE FROM FieldQualifierInts WHERE QualifierId = "
426  .$QualifierIdOrObject);
427  }
428 
429  # return whether qualifier is in use by metadata field
430  function QualifierIsInUse($QualifierIdOrObject)
431  {
432  # sanitize qualifier ID or grab it from object
433  $QualifierIdOrObject = is_object($QualifierIdOrObject)
434  ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
435 
436  # determine whether any fields use qualifier as default
437  $DefaultCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM MetadataFields"
438  ." WHERE DefaultQualifier = ".$QualifierIdOrObject,
439  "RecordCount");
440 
441  # determine whether any fields are associated with qualifier
442  $AssociationCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM FieldQualifierInts"
443  ." WHERE QualifierId = ".$QualifierIdOrObject,
444  "RecordCount");
445 
446  # report whether qualifier is in use based on defaults and associations
447  return (($DefaultCount + $AssociationCount) > 0) ? TRUE : FALSE;
448  }
449 
450  # return highest field ID currently in use
451  function GetHighestFieldId() { return $this->GetHighestItemId(); }
452 
460  static function StdNameToFieldMapping($MappedName, $FieldId = NULL)
461  {
462  if ($FieldId !== NULL)
463  {
464  self::$FieldMappings[$MappedName] = $FieldId;
465  }
466  return isset(self::$FieldMappings[$MappedName])
467  ? self::$FieldMappings[$MappedName] : NULL;
468  }
469 
477  {
478  if ($FieldId != -1)
479  {
480  foreach (self::$FieldMappings as $MappedName => $MappedFieldId)
481  {
482  if ($MappedFieldId == $FieldId)
483  {
484  return $MappedName;
485  }
486  }
487  }
488  return NULL;
489  }
490 
498  function GetFieldByMappedName($MappedName)
499  {
500  return ($this->StdNameToFieldMapping($MappedName) == NULL) ? NULL
501  : $this->GetField($this->StdNameToFieldMapping($MappedName));
502  }
503 
508  public function GetOwnedFields()
509  {
510  $Fields = array();
511 
512  $this->DB->Query("
513  SELECT * FROM MetadataFields
514  WHERE Owner IS NOT NULL AND LENGTH(Owner) > 0");
515 
516  while (FALSE !== ($Row = $this->DB->FetchRow()))
517  {
518  $FieldId = $Row["FieldId"];
519  $Fields[$FieldId] = $this->GetField($FieldId);
520  }
521 
522  return $Fields;
523  }
524 
530  public static function SetOwnerListRetrievalFunction($Callback)
531  {
532  if (is_callable($Callback))
533  {
534  self::$OwnerListRetrievalFunction = $Callback;
535  }
536  }
537 
543  public static function NormalizeOwnedFields()
544  {
545  # if an owner list retrieval function exists
546  if (self::$OwnerListRetrievalFunction)
547  {
548  # retrieve the list of owners that currently exist
549  $OwnerList = call_user_func(self::$OwnerListRetrievalFunction);
550 
551  # an array is expected
552  if (is_array($OwnerList))
553  {
554  $Schema = new MetadataSchema();
555 
556  # get each metadata field that is owned by a plugin
557  $OwnedFields = $Schema->GetOwnedFields();
558 
559  # loop through each owned field
560  foreach ($OwnedFields as $OwnedField)
561  {
562  # the owner of the current field
563  $Owner = $OwnedField->Owner();
564 
565  # if the owner of the field is in the list of owners that
566  # currently exist, i.e., available plugins
567  if (in_array($Owner, $OwnerList))
568  {
569  # enable the field and reset its "enable on owner return"
570  # flag if the "enable on owner return" flag is currently
571  # set to true. in other words, re-enable the field since
572  # the owner has returned to the list of existing owners
573  if ($OwnedField->EnableOnOwnerReturn())
574  {
575  $OwnedField->Enabled(TRUE);
576  $OwnedField->EnableOnOwnerReturn(FALSE);
577  }
578  }
579 
580  # if the owner of the field is *not* in the list of owners
581  # that currently exist, i.e., available plugins
582  else
583  {
584  # first, see if the field is currently enabled since it
585  # will determine whether the field is re-enabled when
586  # the owner becomes available again
587  $Enabled = $OwnedField->Enabled();
588 
589  # if the field is enabled, set its "enable on owner
590  # return" flag to true and disable the field. nothing
591  # needs to be done if the field is already disabled
592  if ($Enabled)
593  {
594  $OwnedField->EnableOnOwnerReturn($Enabled);
595  $OwnedField->Enabled(FALSE);
596  }
597  }
598  }
599  }
600  }
601  }
602 
608  public static function UpdateFieldCompareOrders()
609  {
610  try
611  {
614 
615  $Index = 0;
616 
617  foreach ($DisplayOrder->GetFields() as $Field)
618  {
619  self::$FieldCompareDisplayOrder[$Field->Id()] = $Index++;
620  }
621 
622  $Index = 0;
623 
624  foreach ($EditOrder->GetFields() as $Field)
625  {
626  self::$FieldCompareEditOrder[$Field->Id()] = $Index++;
627  }
628  }
629 
630  catch (Exception $Exception)
631  {
632  # there was an error, so make no assumptions about the order
633  self::$FieldCompareDisplayOrder = array();
634  self::$FieldCompareEditOrder = array();
635  }
636  }
637 
642  protected static function FieldCompareOrdersSet()
643  {
644  return self::$FieldCompareDisplayOrder && self::$FieldCompareEditOrder;
645  }
646 
654  protected function CompareFieldOrder($FieldA, $FieldB)
655  {
656  if ($this->FieldCompareType == MetadataSchema::MDFORDER_ALPHABETICAL)
657  {
658  return ($FieldA->GetDisplayName() < $FieldB->GetDisplayName()) ? -1 : 1;
659  }
660 
661  if ($this->FieldCompareType == MetadataSchema::MDFORDER_EDITING)
662  {
663  $Order = self::$FieldCompareEditOrder;
664  }
665 
666  else
667  {
668  $Order = self::$FieldCompareDisplayOrder;
669  }
670 
671  $PositionA = GetArrayValue($Order, $FieldA->Id(), 0);
672  $PositionB = GetArrayValue($Order, $FieldB->Id(), 0);
673 
674  return $PositionA < $PositionB ? -1 : 1;
675  }
676 
677  # ---- PRIVATE INTERFACE -------------------------------------------------
678 
679  private $FieldCompareType;
680  private $CachingOn;
681  private static $FieldMappings;
682  protected static $OwnerListRetrievalFunction;
683 
687  protected static $FieldCompareDisplayOrder = array();
688 
692  protected static $FieldCompareEditOrder = array();
693 
694 }