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 
00058     # turn internal caching of field info on or off
00059     function CacheData($NewValue)
00060     {
00061         $this->CachingOn = $NewValue;
00062     }
00063 
00064     # add new metadata field
00065     function AddField($FieldName, $FieldType, $Optional = TRUE, $DefaultValue = NULL)
00066     {
00067         # create new field
00068         $Field = new MetadataField(NULL, $FieldName, $FieldType, $Optional, $DefaultValue);
00069 
00070         # save error code if create failed and return NULL
00071         if ($Field->Status() != MetadataSchema::MDFSTAT_OK)
00072         {
00073             $this->ErrorStatus = $Field->Status();
00074             $Field = NULL;
00075         }
00076 
00077         # return new field to caller
00078         return $Field;
00079     }
00080 
00090     function AddFieldFromXml($Xml)
00091     {
00092         # assume field addition will fail
00093         $Field = self::MDFSTAT_ERROR;
00094 
00095         # add XML prefixes if needed
00096         $Xml = trim($Xml);
00097         if (!preg_match("/^<\?xml/i", $Xml))
00098         {
00099             if (!preg_match("/^<document>/i", $Xml))
00100             {
00101                 $Xml = "<document>".$Xml."</document>";
00102             }
00103             $Xml = "<?xml version='1.0'?>".$Xml;
00104         }
00105 
00106         # parse XML
00107         $XmlData = simplexml_load_string($Xml);
00108 
00109          # if required values are present
00110         if (is_object($XmlData)
00111                 && isset($XmlData->Name)
00112                 && isset($XmlData->Type)
00113                 && constant("MetadataSchema::".$XmlData->Type))
00114         {
00115             # create the metadata field
00116             $Field = new MetadataField(NULL, $XmlData->Name,
00117                     constant("MetadataSchema::".$XmlData->Type));
00118 
00119             # if field creation failed
00120             if ($Field->Status() !== self::MDFSTAT_OK)
00121             {
00122                 # reset field value to error code
00123                 $Field = $Field->Status();
00124             }
00125             else
00126             {
00127                 # for other field attributes
00128                 foreach ($XmlData as $MethodName => $Value)
00129                 {
00130                     # if they look valid and have not already been set
00131                     if (method_exists($Field, $MethodName)
00132                             && ($MethodName != "Name")
00133                             && ($MethodName != "Type"))
00134                     {
00135                         # condense down any extraneous whitespace
00136                         $Value = preg_replace("/\s+/", " ", trim($Value));
00137 
00138                         # set value for field
00139                         $Field->$MethodName($Value);
00140                     }
00141                 }
00142 
00143                 # make new field permanent
00144                 $Field->IsTempItem(FALSE);
00145             }
00146         }
00147 
00148         # return new field (if any) to caller
00149         return $Field;
00150     }
00151 
00152     # delete metadata field
00153     function DropField($FieldId)
00154     {
00155         $Field = new MetadataField($FieldId);
00156         $Field->Drop();
00157     }
00158 
00159     # retrieve field by ID
00160     function GetField($FieldId)
00161     {
00162         static $Fields;
00163 
00164         # if caching is off or field is already loaded
00165         if (($this->CachingOn != TRUE) || !isset($Fields[$FieldId]))
00166         {
00167             # retrieve field
00168             $Fields[$FieldId] = new MetadataField($FieldId);
00169         }
00170 
00171         # return field to caller
00172         return $Fields[$FieldId];
00173     }
00174 
00181     function GetFieldByName($FieldName, $IgnoreCase = FALSE)
00182     {
00183         $FieldId = $this->GetFieldIdByName($FieldName, $IgnoreCase);
00184         return ($FieldId === NULL) ? NULL : $this->GetField($FieldId);
00185     }
00186 
00193     function GetFieldByLabel($FieldLabel, $IgnoreCase = FALSE)
00194     {
00195         $FieldId = $this->GetFieldIdByLabel($FieldLabel, $IgnoreCase);
00196         return ($FieldId === NULL) ? NULL : $this->GetField($FieldId);
00197     }
00198 
00206     function GetFieldIdByName($FieldName, $IgnoreCase = FALSE)
00207     {
00208         static $FieldIdsByName;
00209 
00210         # if caching is off or field ID is already loaded
00211         if (($this->CachingOn != TRUE) || !isset($FieldIdsByName[$FieldName]))
00212         {
00213             # retrieve field ID from DB
00214             $Condition = $IgnoreCase
00215                     ? "WHERE LOWER(FieldName) = '".addslashes(strtolower($FieldName))."'"
00216                     : "WHERE FieldName = '".addslashes($FieldName)."'";
00217             $FieldIdsByName[$FieldName] = $this->DB->Query(
00218                     "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId");
00219         }
00220 
00221         return $FieldIdsByName[$FieldName];
00222     }
00223 
00231     function GetFieldIdByLabel($FieldLabel, $IgnoreCase = FALSE)
00232     {
00233         static $FieldIdsByLabel;
00234 
00235         # if caching is off or field ID is already loaded
00236         if (($this->CachingOn != TRUE) || !isset($FieldIdsByLabel[$FieldLabel]))
00237         {
00238             # retrieve field ID from DB
00239             $Condition = $IgnoreCase
00240                     ? "WHERE LOWER(Label) = '".addslashes(strtolower($FieldLabel))."'"
00241                     : "WHERE Label = '".addslashes($FieldLabel)."'";
00242             $FieldIdsByLabel[$FieldLabel] = $this->DB->Query(
00243                     "SELECT FieldId FROM MetadataFields ".$Condition, "FieldId");
00244         }
00245 
00246         return $FieldIdsByLabel[$FieldLabel];
00247     }
00248 
00249     # check whether field with specified name exists
00250     function FieldExists($FieldName) {  return $this->NameIsInUse($FieldName);  }
00251 
00252     # retrieve array of fields
00253     function GetFields($FieldTypes = NULL, $OrderType = NULL,
00254             $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
00255     {
00256         # create empty array to pass back
00257         $Fields = array();
00258 
00259         # for each field type in database
00260         if ($IncludeTempFields && $IncludeDisabledFields)
00261         {
00262             $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields");
00263         }
00264         else
00265         {
00266             if ($IncludeTempFields)
00267             {
00268                 $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE Enabled != 0");
00269             }
00270             elseif ($IncludeDisabledFields)
00271             {
00272                 $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0");
00273             }
00274             else
00275             {
00276                 $this->DB->Query("SELECT FieldId, FieldType FROM MetadataFields WHERE FieldId >= 0 AND Enabled != 0");
00277             }
00278         }
00279         while ($Record = $this->DB->FetchRow())
00280         {
00281             # if no specific type requested or if field is of requested type
00282             if (($FieldTypes == NULL)
00283                 || (MetadataField::$FieldTypePHPEnums[$Record["FieldType"]] & $FieldTypes))
00284             {
00285                 # create field object and add to array to be passed back
00286                 $Fields[$Record["FieldId"]] = $this->GetField($Record["FieldId"]);
00287             }
00288         }
00289 
00290         # if field sorting requested
00291         if ($OrderType !== NULL)
00292         {
00293             # sort field array by requested order type
00294             $this->FieldCompareType = $OrderType;
00295             $this->FieldOrderError = FALSE;
00296             uasort($Fields, array($this, "CompareFieldOrder"));
00297 
00298             # if field order error detected
00299             if ($this->FieldOrderError)
00300             {
00301                 # repair (reset) field order
00302                 $OrderIndex = 1;
00303                 foreach ($Fields as $Field)
00304                 {
00305                     $Field->OrderPosition($OrderType, $OrderIndex);
00306                     $OrderIndex++;
00307                 }
00308             }
00309         }
00310 
00311         # return array of field objects to caller
00312         return $Fields;
00313     }
00314 
00315     # callback function for sorting fields
00316     function CompareFieldOrder($FieldA, $FieldB)
00317     {
00318         if ($this->FieldCompareType == MetadataSchema::MDFORDER_ALPHABETICAL)
00319         {
00320             return ($FieldA->GetDisplayName() < $FieldB->GetDisplayName()) ? -1 : 1;
00321         }
00322         else
00323         {
00324             if ($FieldA->OrderPosition($this->FieldCompareType)
00325                     == $FieldB->OrderPosition($this->FieldCompareType))
00326             {
00327                 $this->FieldOrderError = TRUE;
00328                 return 0;
00329             }
00330             else
00331             {
00332                 return ($FieldA->OrderPosition($this->FieldCompareType)
00333                         < $FieldB->OrderPosition($this->FieldCompareType)) ? -1 : 1;
00334             }
00335         }
00336     }
00337 
00338     function GetFieldNames($FieldTypes = NULL, $OrderType = NULL,
00339                             $IncludeDisabledFields = FALSE, $IncludeTempFields = FALSE)
00340     {
00341         global $DB;
00342 
00343         $FieldNames=array();
00344         $Fields = $this->GetFields($FieldTypes, $OrderType, $IncludeDisabledFields, $IncludeTempFields);
00345 
00346         foreach($Fields as $Field)
00347         {
00348             $DB->Query("SELECT FieldName FROM MetadataFields WHERE FieldId=".$Field->Id());
00349             $FieldNames[ $Field->Id() ] = $DB->FetchField("FieldName");
00350         }
00351 
00352         return $FieldNames;
00353     }
00354 
00370     function GetFieldsAsOptionList($OptionListName, $FieldTypes = NULL,
00371             $SelectedFieldId = NULL, $IncludeNullOption = TRUE,
00372             $AddEntries = NULL, $AllowMultiple = FALSE)
00373     {
00374         # retrieve requested fields
00375         $FieldNames = $this->GetFieldNames($FieldTypes);
00376 
00377         # transform field names to labels
00378         foreach ($FieldNames as $FieldId => $FieldName)
00379         {
00380             $FieldNames[$FieldId] = $this->GetField($FieldId)->GetDisplayName();
00381         }
00382 
00383         # begin HTML option list
00384         $Html = "<select id=\"".$OptionListName."\" name=\"".$OptionListName."\"";
00385 
00386         # if multiple selections should be allowed
00387         if ($AllowMultiple)
00388         {
00389             $Html .= " multiple=\"multiple\"";
00390         }
00391 
00392         $Html .= ">\n";
00393 
00394         if ($IncludeNullOption)
00395         {
00396             $Html .= "<option value=\"\">--</option>\n";
00397         }
00398 
00399         # make checking for IDs simpler
00400         if (!is_array($SelectedFieldId))
00401         {
00402             $SelectedFieldId = array($SelectedFieldId);
00403         }
00404 
00405         # for each metadata field
00406         foreach ($FieldNames as $Id => $Name)
00407         {
00408             # add entry for field to option list
00409             $Html .= "<option value=\"".$Id."\"";
00410             if (in_array($Id, $SelectedFieldId)) {  $Html .= " selected";  }
00411             $Html .= ">".htmlspecialchars($Name)."</option>\n";
00412         }
00413 
00414         # if additional entries were requested
00415         if ($AddEntries)
00416         {
00417             foreach ($AddEntries as $Value => $Label)
00418             {
00419                 $Html .= "<option value=\"".$Value."\"";
00420                 if (in_array($Value,$SelectedFieldId)) {  $Html .= " selected";  }
00421                 $Html .= ">".htmlspecialchars($Label)."</option>\n";
00422             }
00423         }
00424 
00425         # end HTML option list
00426         $Html .= "</select>\n";
00427 
00428         # return constructed HTML to caller
00429         return $Html;
00430     }
00431 
00432     # retrieve array of field types (enumerated type => field name)
00433     function GetFieldTypes()
00434     {
00435         return MetadataField::$FieldTypeDBEnums;
00436     }
00437 
00438     # retrieve array of field types that user can create (enumerated type => field name)
00439     function GetAllowedFieldTypes()
00440     {
00441         return MetadataField::$FieldTypeDBAllowedEnums;
00442     }
00443 
00444     # remove all metadata field associations for a given qualifier
00445     function RemoveQualifierAssociations($QualifierIdOrObject)
00446     {
00447         # sanitize qualifier ID or grab it from object
00448         $QualifierIdOrObject = is_object($QualifierIdOrObject)
00449                 ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
00450 
00451         # delete intersection records from database
00452         $this->DB->Query("DELETE FROM FieldQualifierInts WHERE QualifierId = "
00453                          .$QualifierIdOrObject);
00454     }
00455 
00456     # return whether qualifier is in use by metadata field
00457     function QualifierIsInUse($QualifierIdOrObject)
00458     {
00459         # sanitize qualifier ID or grab it from object
00460         $QualifierIdOrObject = is_object($QualifierIdOrObject)
00461                 ? $QualifierIdOrObject->Id() : intval($QualifierIdOrObject);
00462 
00463         # determine whether any fields use qualifier as default
00464         $DefaultCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM MetadataFields"
00465                                          ." WHERE DefaultQualifier = ".$QualifierIdOrObject,
00466                                          "RecordCount");
00467 
00468         # determine whether any fields are associated with qualifier
00469         $AssociationCount = $this->DB->Query("SELECT COUNT(*) AS RecordCount FROM FieldQualifierInts"
00470                                          ." WHERE QualifierId = ".$QualifierIdOrObject,
00471                                          "RecordCount");
00472 
00473         # report whether qualifier is in use based on defaults and associations
00474         return (($DefaultCount + $AssociationCount) > 0) ? TRUE : FALSE;
00475     }
00476 
00477     # move fields up or down in field order
00478     function MoveUpInOrder($FieldIdOrObj, $OrderType)
00479     {
00480         $this->MoveFieldInOrder($FieldIdOrObj, $OrderType, FALSE);
00481     }
00482     function MoveDownInOrder($FieldIdOrObj, $OrderType)
00483     {
00484         $this->MoveFieldInOrder($FieldIdOrObj, $OrderType, TRUE);
00485     }
00486 
00487     # return highest field ID currently in use
00488     function GetHighestFieldId() {  return $this->GetHighestItemId();  }
00489 
00497     static function StdNameToFieldMapping($MappedName, $FieldId = NULL)
00498     {
00499         if ($FieldId !== NULL)
00500         {
00501             self::$FieldMappings[$MappedName] = $FieldId;
00502         }
00503         return isset(self::$FieldMappings[$MappedName])
00504                 ? self::$FieldMappings[$MappedName] : NULL;
00505     }
00506 
00513     static function FieldToStdNameMapping($FieldId)
00514     {
00515         if ($FieldId != -1)
00516         {
00517             foreach (self::$FieldMappings as $MappedName => $MappedFieldId)
00518             {
00519                 if ($MappedFieldId == $FieldId)
00520                 {
00521                     return $MappedName;
00522                 }
00523             }
00524         }
00525         return NULL;
00526     }
00527 
00535     function GetFieldByMappedName($MappedName)
00536     {
00537         return ($this->StdNameToFieldMapping($MappedName) == NULL) ? NULL
00538                 : $this->GetField($this->StdNameToFieldMapping($MappedName));
00539     }
00540 
00545     public function GetOwnedFields()
00546     {
00547         $Fields = array();
00548 
00549         $this->DB->Query("
00550             SELECT * FROM MetadataFields
00551             WHERE Owner IS NOT NULL AND LENGTH(Owner) > 0");
00552 
00553         while (FALSE !== ($Row = $this->DB->FetchRow()))
00554         {
00555             $FieldId = $Row["FieldId"];
00556             $Fields[$FieldId] = $this->GetField($FieldId);
00557         }
00558 
00559         return $Fields;
00560     }
00561 
00567     public static function SetOwnerListRetrievalFunction($Callback)
00568     {
00569         if (is_callable($Callback))
00570         {
00571             self::$OwnerListRetrievalFunction = $Callback;
00572         }
00573     }
00574 
00580     public static function NormalizeOwnedFields()
00581     {
00582         # if an owner list retrieval function exists
00583         if (self::$OwnerListRetrievalFunction)
00584         {
00585             # retrieve the list of owners that currently exist
00586             $OwnerList = call_user_func(self::$OwnerListRetrievalFunction);
00587 
00588             # an array is expected
00589             if (is_array($OwnerList))
00590             {
00591                 $Schema = new MetadataSchema();
00592 
00593                 # get each metadata field that is owned by a plugin
00594                 $OwnedFields = $Schema->GetOwnedFields();
00595 
00596                 # loop through each owned field
00597                 foreach ($OwnedFields as $OwnedField)
00598                 {
00599                     # the owner of the current field
00600                     $Owner = $OwnedField->Owner();
00601 
00602                     # if the owner of the field is in the list of owners that
00603                     # currently exist, i.e., available plugins
00604                     if (in_array($Owner, $OwnerList))
00605                     {
00606                         # enable the field and reset its "enable on owner return"
00607                         # flag if the "enable on owner return" flag is currently
00608                         # set to true. in other words, re-enable the field since
00609                         # the owner has returned to the list of existing owners
00610                         if ($OwnedField->EnableOnOwnerReturn())
00611                         {
00612                             $OwnedField->Enabled(TRUE);
00613                             $OwnedField->EnableOnOwnerReturn(FALSE);
00614                         }
00615                     }
00616 
00617                     # if the owner of the field is *not* in the list of owners
00618                     # that currently exist, i.e., available plugins
00619                     else
00620                     {
00621                         # first, see if the field is currently enabled since it
00622                         # will determine whether the field is re-enabled when
00623                         # the owner becomes available again
00624                         $Enabled = $OwnedField->Enabled();
00625 
00626                         # if the field is enabled, set its "enable on owner
00627                         # return" flag to true and disable the field. nothing
00628                         # needs to be done if the field is already disabled
00629                         if ($Enabled)
00630                         {
00631                             $OwnedField->EnableOnOwnerReturn($Enabled);
00632                             $OwnedField->Enabled(FALSE);
00633                         }
00634                     }
00635                 }
00636             }
00637         }
00638     }
00639 
00640     # ---- PRIVATE INTERFACE -------------------------------------------------
00641 
00642     private $FieldCompareType;
00643     private $FieldOrderError;
00644     private $CachingOn;
00645     private static $FieldMappings;
00646     protected static $OwnerListRetrievalFunction;
00647 
00648     private function MoveFieldInOrder($FieldIdOrObj, $OrderType, $MoveFieldDown)
00649     {
00650         # grab field ID
00651         $FieldId = is_object($FieldIdOrObj) ? $Field->Id() : $FieldIdOrObj;
00652 
00653         # retrieve array of fields
00654         $Fields = $this->GetFields(NULL, $OrderType);
00655 
00656         # reverse array of fields if we are moving field down
00657         if ($MoveFieldDown)
00658         {
00659             $Fields = array_reverse($Fields);
00660         }
00661 
00662         # for each field in order
00663         $PreviousField = NULL;
00664         foreach ($Fields as $Field)
00665         {
00666             # if field is the field to be moved
00667             if ($Field->Id() == $FieldId)
00668             {
00669                 # if we have a previous field
00670                 if ($PreviousField !== NULL)
00671                 {
00672                     # swap field with previous field according to order type
00673                     $TempVal = $Field->OrderPosition($OrderType);
00674                     $Field->OrderPosition($OrderType, $PreviousField->OrderPosition($OrderType));
00675                     $PreviousField->OrderPosition($OrderType, $TempVal);
00676                 }
00677             }
00678 
00679             # save field for next iteration
00680             $PreviousField = $Field;
00681         }
00682     }
00683 }