MetadataSchema.php
Go to the documentation of this file.
00001 <?PHP 00002 # 00003 # FILE: MetadataSchema.php 00004 # 00005 # Part of the Collection Workflow Integration System (CWIS) 00006 # Copyright 2011 Edward Almasy and Internet Scout 00007 # http://scout.wisc.edu 00008 # 00009 00010 class MetadataSchema extends ItemFactory { 00011 00012 # ---- PUBLIC INTERFACE -------------------------------------------------- 00013 00014 # types of field ordering 00015 const MDFORDER_DISPLAY = 1; 00016 const MDFORDER_EDITING = 2; 00017 const MDFORDER_ALPHABETICAL = 3; 00018 00019 # metadata field types 00020 # (must parallel MetadataFields.FieldType declaration in install/CreateTables.sql 00021 # and MetadataField::$FieldTypeDBEnums declaration below) 00022 const MDFTYPE_TEXT = 1; 00023 const MDFTYPE_PARAGRAPH = 2; 00024 const MDFTYPE_NUMBER = 4; 00025 const MDFTYPE_DATE = 8; 00026 const MDFTYPE_TIMESTAMP = 16; 00027 const MDFTYPE_FLAG = 32; 00028 const MDFTYPE_TREE = 64; 00029 const MDFTYPE_CONTROLLEDNAME = 128; 00030 const MDFTYPE_OPTION = 256; 00031 const MDFTYPE_USER = 512; 00032 const MDFTYPE_IMAGE = 1024; 00033 const MDFTYPE_FILE = 2048; 00034 const MDFTYPE_URL = 4096; 00035 const MDFTYPE_POINT = 8192; 00036 00037 # error status codes 00038 const MDFSTAT_OK = 1; 00039 const MDFSTAT_ERROR = 2; 00040 const MDFSTAT_DUPLICATENAME = 4; 00041 const MDFSTAT_DUPLICATEDBCOLUMN = 8; 00042 const MDFSTAT_FIELDDOESNOTEXIST = 16; 00043 const MDFSTAT_ILLEGALNAME = 32; 00044 const MDFSTAT_DUPLICATELABEL = 64; 00045 const MDFSTAT_ILLEGALLABEL = 128; 00046 00047 # object constructor 00048 function MetadataSchema() 00049 { 00050 # set up item factory base class 00051 $this->ItemFactory( 00052 "MetadataField", "MetadataFields", "FieldId", "FieldName"); 00053 00054 # start with field info caching enabled 00055 $this->CachingOn = TRUE; 00056 00057 if (self::$OwnerListRetrievalFunction) 00058 { 00059 $OwnerList = call_user_func(self::$OwnerListRetrievalFunction); 00060 00061 if (is_array($OwnerList)) 00062 { 00063 $OwnedFields = $this->GetOwnedFields(); 00064 00065 foreach ($OwnedFields as $OwnedField) 00066 { 00067 $Owner = $OwnedField->Owner(); 00068 00069 if (in_array($Owner, $OwnerList)) 00070 { 00071 if ($OwnedField->EnableOnOwnerReturn()) 00072 { 00073 $OwnedField->Enabled(TRUE); 00074 $OwnedField->EnableOnOwnerReturn(FALSE); 00075 } 00076 } 00077 00078 else 00079 { 00080 $Enabled = $OwnedField->Enabled(); 00081 00082 if ($Enabled) 00083 { 00084 $OwnedField->EnableOnOwnerReturn($Enabled); 00085 $OwnedField->Enabled(FALSE); 00086 } 00087 } 00088 } 00089 } 00090 } 00091 } 00092 00093 # turn internal caching of field info on or off 00094 function CacheData($NewValue) 00095 { 00096 $this->CachingOn = $NewValue; 00097 } 00098 00099 # add new metadata field 00100 function AddField($FieldName, $FieldType, $Optional = TRUE, $DefaultValue = NULL) 00101 { 00102 # create new field 00103 $Field = new MetadataField(NULL, $FieldName, $FieldType, $Optional, $DefaultValue); 00104 00105 # save error code if create failed and return NULL 00106 if ($Field->Status() != MetadataSchema::MDFSTAT_OK) 00107 { 00108 $this->ErrorStatus = $Field->Status(); 00109 $Field = NULL; 00110 } 00111 00112 # return new field to caller 00113 return $Field; 00114 } 00115 00125 function AddFieldFromXml($Xml) 00126 { 00127 # assume field addition will fail 00128 $Field = self::MDFSTAT_ERROR; 00129 00130 # add XML prefixes if needed 00131 $Xml = trim($Xml); 00132 if (!preg_match("/^<\?xml/i", $Xml)) 00133 { 00134 if (!preg_match("/^<document>/i", $Xml)) 00135 { 00136 $Xml = "<document>".$Xml."</document>"; 00137 } 00138 $Xml = "<?xml version='1.0'?>".$Xml; 00139 } 00140 00141 # parse XML 00142 $XmlData = simplexml_load_string($Xml); 00143 00144 # if required values are present 00145 if (is_object($XmlData) 00146 && isset($XmlData->Name) 00147 && isset($XmlData->Type) 00148 && constant("MetadataSchema::".$XmlData->Type)) 00149 { 00150 # create the metadata field 00151 $Field = new MetadataField(NULL, $XmlData->Name, 00152 constant("MetadataSchema::".$XmlData->Type)); 00153 00154 # if field creation failed 00155 if ($Field->Status() !== self::MDFSTAT_OK) 00156 { 00157 # reset field value to error code 00158 $Field = $Field->Status(); 00159 } 00160 else 00161 { 00162 # for other field attributes 00163 foreach ($XmlData as $MethodName => $Value) 00164 { 00165 # if they look valid and have not already been set 00166 if (method_exists($Field, $MethodName) 00167 && ($MethodName != "Name") 00168 && ($MethodName != "Type")) 00169 { 00170 # condense down any extraneous whitespace 00171 $Value = preg_replace("/\s+/", " ", trim($Value)); 00172 00173 # set value for field 00174 $Field->$MethodName($Value); 00175 } 00176 } 00177 00178 # make new field permanent 00179 $Field->IsTempItem(FALSE); 00180 } 00181 } 00182 00183 # return new field (if any) to caller 00184 return $Field; 00185 } 00186 00187 # delete metadata field 00188 function DropField($FieldId) 00189 { 00190 $Field = new MetadataField($FieldId); 00191 $Field->Drop(); 00192 } 00193 00194 # retrieve field by ID 00195 function GetField($FieldId) 00196 { 00197 static $Fields; 00198 00199 # if caching is off or field is already loaded 00200 if (($this->CachingOn != TRUE) || !isset($Fields[$FieldId])) 00201 { 00202 # retrieve field 00203 $Fields[$FieldId] = new MetadataField($FieldId); 00204 } 00205 00206 # return field to caller 00207 return $Fields[$FieldId]; 00208 } 00209 00216 function GetFieldByName($FieldName, $IgnoreCase = FALSE) 00217 { 00218 $FieldId = $this->GetFieldIdByName($FieldName, $IgnoreCase); 00219 return ($FieldId === NULL) ? NULL : $this->GetField($FieldId); 00220 } 00221 00228 function GetFieldByLabel($FieldLabel, $IgnoreCase = FALSE) 00229 { 00230 $FieldId = $this->GetFieldIdByLabel($FieldLabel, $IgnoreCase); 00231 return ($FieldId === NULL) ? NULL : $this->GetField($FieldId); 00232 } 00233 00241 function GetFieldIdByName($FieldName, $IgnoreCase = FALSE) 00242 { 00243 static $FieldIdsByName; 00244 00245 # if caching is off or field ID is already loaded 00246 if (($this->CachingOn != TRUE) || !isset($FieldIdsByName[$FieldName])) 00247 { 00248 # retrieve field ID from DB 00249 $Condition = $IgnoreCase 00250 ? "WHERE LOWER(FieldName) = '".addslashes(strtolower($FieldName))."'" 00251 : "WHERE FieldName = '".addslashes($FieldName)."'"; 00252 $FieldIdsByName[$FieldName] = $this->DB->Query( 00253 "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId"); 00254 } 00255 00256 return $FieldIdsByName[$FieldName]; 00257 } 00258 00266 function GetFieldIdByLabel($FieldLabel, $IgnoreCase = FALSE) 00267 { 00268 static $FieldIdsByLabel; 00269 00270 # if caching is off or field ID is already loaded 00271 if (($this->CachingOn != TRUE) || !isset($FieldIdsByLabel[$FieldLabel])) 00272 { 00273 # retrieve field ID from DB 00274 $Condition = $IgnoreCase 00275 ? "WHERE LOWER(Label) = '".addslashes(strtolower($FieldLabel))."'" 00276 : "WHERE Label = '".addslashes($FieldLabel)."'"; 00277 $FieldIdsByLabel[$FieldLabel] = $this->DB->Query( 00278 "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId"); 00279 } 00280 00281 return $FieldIdsByLabel[$FieldLabel]; 00282 } 00283 00284 # check whether field with specified name exists 00285 function FieldExists($FieldName) { return $this->NameIsInUse($FieldName); } 00286 00287 # retrieve array of fields 00288 function GetFields($FieldTypes = NULL, $OrderType = NULL, 00289 $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE) 00290 { 00291 # create empty array to pass back 00292 $Fields = array(); 00293 00294 # for each field type in database 00295 if ($IncludeTempFields && $IncludeDisabledFields) 00296 { 00297 $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields"); 00298 } 00299 else 00300 { 00301 if ($IncludeTempFields) 00302 { 00303 $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE Enabled != 0"); 00304 } 00305 elseif ($IncludeDisabledFields) 00306 { 00307 $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0"); 00308 } 00309 else 00310 { 00311 $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0 AND Enabled != 0"); 00312 } 00313 } 00314 while ($Record = $this->DB->FetchRow()) 00315 { 00316 # if no specific type requested or if field is of requested type 00317 if (($FieldTypes == NULL) 00318 || (MetadataField::$FieldTypePHPEnums[$Record["FieldType"]] & $FieldTypes)) 00319 { 00320 # create field object and add to array to be passed back 00321 $Fields[$Record["FieldId"]] = new MetadataField($Record["FieldId"]); 00322 } 00323 } 00324 00325 # if field sorting requested 00326 if ($OrderType !== NULL) 00327 { 00328 # sort field array by requested order type 00329 $this->FieldCompareType = $OrderType; 00330 $this->FieldOrderError = FALSE; 00331 uasort($Fields, array($this, "CompareFieldOrder")); 00332 00333 # if field order error detected 00334 if ($this->FieldOrderError) 00335 { 00336 # repair (reset) field order 00337 $OrderIndex = 1; 00338 foreach ($Fields as $Field) 00339 { 00340 $Field->OrderPosition($OrderType, $OrderIndex); 00341 $OrderIndex++; 00342 } 00343 } 00344 } 00345 00346 # return array of field objects to caller 00347 return $Fields; 00348 } 00349 00350 # callback function for sorting fields 00351 function CompareFieldOrder($FieldA, $FieldB) 00352 { 00353 if ($this->FieldCompareType == MetadataSchema::MDFORDER_ALPHABETICAL) 00354 { 00355 return ($FieldA->GetDisplayName() < $FieldB->GetDisplayName()) ? -1 : 1; 00356 } 00357 else 00358 { 00359 if ($FieldA->OrderPosition($this->FieldCompareType) 00360 == $FieldB->OrderPosition($this->FieldCompareType)) 00361 { 00362 $this->FieldOrderError = TRUE; 00363 return 0; 00364 } 00365 else 00366 { 00367 return ($FieldA->OrderPosition($this->FieldCompareType) 00368 < $FieldB->OrderPosition($this->FieldCompareType)) ? -1 : 1; 00369 } 00370 } 00371 } 00372 00373 function GetFieldNames($FieldTypes = NULL, $OrderType = NULL, 00374 $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE) 00375 { 00376 global $DB; 00377 00378 $FieldNames=array(); 00379 $Fields = $this->GetFields($FieldTypes, $OrderType, $IncludeDisabledFields, $IncludeTempFields); 00380 00381 foreach($Fields as $Field) 00382 { 00383 $DB->Query("SELECT FieldName FROM MetadataFields WHERE FieldId=".$Field->Id()); 00384 $FieldNames[ $Field->Id() ] = $DB->FetchField("FieldName"); 00385 } 00386 00387 return $FieldNames; 00388 } 00389 00405 function GetFieldsAsOptionList($OptionListName, $FieldTypes = NULL, 00406 $SelectedFieldId = NULL, $IncludeNullOption = TRUE, 00407 $AddEntries = NULL, $AllowMultiple = FALSE) 00408 { 00409 # retrieve requested fields 00410 $FieldNames = $this->GetFieldNames($FieldTypes); 00411 00412 # transform field names to labels 00413 foreach ($FieldNames as $FieldId => $FieldName) 00414 { 00415 $FieldNames[$FieldId] = $this->GetField($FieldId)->GetDisplayName(); 00416 } 00417 00418 # begin HTML option list 00419 $Html = "<select id=\"".$OptionListName."\" name=\"".$OptionListName."\""; 00420 00421 # if multiple selections should be allowed 00422 if ($AllowMultiple) 00423 { 00424 $Html .= " multiple=\"multiple\""; 00425 } 00426 00427 $Html .= ">\n"; 00428 00429 if ($IncludeNullOption) 00430 { 00431 $Html .= "<option value=\"\">--</option>\n"; 00432 } 00433 00434 # make checking for IDs simpler 00435 if (!is_array($SelectedFieldId)) 00436 { 00437 $SelectedFieldId = array($SelectedFieldId); 00438 } 00439 00440 # for each metadata field 00441 foreach ($FieldNames as $Id => $Name) 00442 { 00443 # add entry for field to option list 00444 $Html .= "<option value=\"".$Id."\""; 00445 if (in_array($Id, $SelectedFieldId)) { $Html .= " selected"; } 00446 $Html .= ">".htmlspecialchars($Name)."</option>\n"; 00447 } 00448 00449 # if additional entries were requested 00450 if ($AddEntries) 00451 { 00452 foreach ($AddEntries as $Value => $Label) 00453 { 00454 $Html .= "<option value=\"".$Value."\""; 00455 if ($Value == $SelectedFieldId) { $Html .= " selected"; } 00456 $Html .= ">".htmlspecialchars($Label)."</option>\n"; 00457 } 00458 } 00459 00460 # end HTML option list 00461 $Html .= "</select>\n"; 00462 00463 # return constructed HTML to caller 00464 return $Html; 00465 } 00466 00467 # retrieve array of field types (enumerated type => field name) 00468 function GetFieldTypes() 00469 { 00470 return MetadataField::$FieldTypeDBEnums; 00471 } 00472 00473 # retrieve array of field types that user can create (enumerated type => field name) 00474 function GetAllowedFieldTypes() 00475 { 00476 return MetadataField::$FieldTypeDBAllowedEnums; 00477 } 00478 00479 # remove all metadata field associations for a given qualifier 00480 function RemoveQualifierAssociations($QualifierIdOrObject) 00481 { 00482 # sanitize qualifier ID or grab it from object 00483 $QualifierIdOrObject = is_object($QualifierIdOrObject) 00484 ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject); 00485 00486 # delete intersection records from database 00487 $this->DB->Query("DELETE FROM FieldQualifierInts WHERE QualifierId = " 00488 .$QualifierIdOrObject); 00489 } 00490 00491 # return whether qualifier is in use by metadata field 00492 function QualifierIsInUse($QualifierIdOrObject) 00493 { 00494 # sanitize qualifier ID or grab it from object 00495 $QualifierIdOrObject = is_object($QualifierIdOrObject) 00496 ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject); 00497 00498 # determine whether any fields use qualifier as default 00499 $DefaultCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM MetadataFields" 00500 ." WHERE DefaultQualifier = ".$QualifierIdOrObject, 00501 "RecordCount"); 00502 00503 # determine whether any fields are associated with qualifier 00504 $AssociationCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM FieldQualifierInts" 00505 ." WHERE QualifierId = ".$QualifierIdOrObject, 00506 "RecordCount"); 00507 00508 # report whether qualifier is in use based on defaults and associations 00509 return (($DefaultCount + $AssociationCount) > 0) ? TRUE : FALSE; 00510 } 00511 00512 # move fields up or down in field order 00513 function MoveUpInOrder($FieldIdOrObj, $OrderType) 00514 { 00515 $this->MoveFieldInOrder($FieldIdOrObj, $OrderType, FALSE); 00516 } 00517 function MoveDownInOrder($FieldIdOrObj, $OrderType) 00518 { 00519 $this->MoveFieldInOrder($FieldIdOrObj, $OrderType, TRUE); 00520 } 00521 00522 # return highest field ID currently in use 00523 function GetHighestFieldId() { return $this->GetHighestItemId(); } 00524 00532 static function StdNameToFieldMapping($MappedName, $FieldId = NULL) 00533 { 00534 if ($FieldId !== NULL) 00535 { 00536 self::$FieldMappings[$MappedName] = $FieldId; 00537 } 00538 return isset(self::$FieldMappings[$MappedName]) 00539 ? self::$FieldMappings[$MappedName] : NULL; 00540 } 00541 00548 static function FieldToStdNameMapping($FieldId) 00549 { 00550 if ($FieldId != -1) 00551 { 00552 foreach (self::$FieldMappings as $MappedName => $MappedFieldId) 00553 { 00554 if ($MappedFieldId == $FieldId) 00555 { 00556 return $MappedName; 00557 } 00558 } 00559 } 00560 return NULL; 00561 } 00562 00570 function GetFieldByMappedName($MappedName) 00571 { 00572 return ($this->StdNameToFieldMapping($MappedName) == NULL) ? NULL 00573 : $this->GetField($this->StdNameToFieldMapping($MappedName)); 00574 } 00575 00580 public function GetOwnedFields() 00581 { 00582 $Fields = array(); 00583 00584 $this->DB->Query(" 00585 SELECT * FROM MetadataFields 00586 WHERE Owner IS NOT NULL AND LENGTH(Owner) > 0"); 00587 00588 while (FALSE !== ($Row = $this->DB->FetchRow())) 00589 { 00590 $FieldId = $Row["FieldId"]; 00591 $Fields[$FieldId] = new MetadataField($FieldId); 00592 } 00593 00594 return $Fields; 00595 } 00596 00602 public static function SetOwnerListRetrievalFunction($Callback) 00603 { 00604 if (is_callable($Callback)) 00605 { 00606 self::$OwnerListRetrievalFunction = $Callback; 00607 } 00608 } 00609 00610 # ---- PRIVATE INTERFACE ------------------------------------------------- 00611 00612 private $FieldCompareType; 00613 private $FieldOrderError; 00614 private $CachingOn; 00615 private static $FieldMappings; 00616 protected static $OwnerListRetrievalFunction; 00617 00618 private function MoveFieldInOrder($FieldIdOrObj, $OrderType, $MoveFieldDown) 00619 { 00620 # grab field ID 00621 $FieldId = is_object($FieldIdOrObj) ? $Field->Id() : $FieldIdOrObj; 00622 00623 # retrieve array of fields 00624 $Fields = $this->GetFields(NULL, $OrderType); 00625 00626 # reverse array of fields if we are moving field down 00627 if ($MoveFieldDown) 00628 { 00629 $Fields = array_reverse($Fields); 00630 } 00631 00632 # for each field in order 00633 $PreviousField = NULL; 00634 foreach ($Fields as $Field) 00635 { 00636 # if field is the field to be moved 00637 if ($Field->Id() == $FieldId) 00638 { 00639 # if we have a previous field 00640 if ($PreviousField !== NULL) 00641 { 00642 # swap field with previous field according to order type 00643 $TempVal = $Field->OrderPosition($OrderType); 00644 $Field->OrderPosition($OrderType, $PreviousField->OrderPosition($OrderType)); 00645 $PreviousField->OrderPosition($OrderType, $TempVal); 00646 } 00647 } 00648 00649 # save field for next iteration 00650 $PreviousField = $Field; 00651 } 00652 } 00653 } 00654