CWIS Developer Documentation

- Home
- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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;
17 const MDFORDER_ALPHABETICAL = 3;
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;
29 const MDFTYPE_CONTROLLEDNAME = 128;
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;
40 const MDFSTAT_DUPLICATENAME = 4;
41 const MDFSTAT_DUPLICATEDBCOLUMN = 8;
42 const MDFSTAT_FIELDDOESNOTEXIST = 16;
43 const MDFSTAT_ILLEGALNAME = 32;
44 const MDFSTAT_DUPLICATELABEL = 64;
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 {
408 return MetadataField::$FieldTypeDBEnums;
409 }
410
411 # retrieve array of field types that user can create (enumerated type => field name)
412 function GetAllowedFieldTypes()
413 {
414 return MetadataField::$FieldTypeDBAllowedEnums;
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
476 static function FieldToStdNameMapping($FieldId)
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 {
612 $DisplayOrder = MetadataFieldOrder::GetDisplayOrderObject();
613 $EditOrder = MetadataFieldOrder::GetEditOrderObject();
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 }