Search:

# CWIS Developers Documentation

- Main Page
- Classes
- Files

- File List
- File Members

# 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 }