From 8c4e84dfafa81e0359259b2516a52313c939fe4b Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 13 Jan 2016 14:35:21 +0000 Subject: [PATCH] New type of attribute: AttributeMetaEnum. Designed to cope with the need to select tickets by operational status. The value of this attribute is computed by the framework. It depends on the actual ticket status (that attribute cannot be known by the root class because its definition varies from one type of ticket to another). The data model has been enriched with the new attribute Ticket::operational_status. Its value is 'active' unless the ticket status is either 'rejected', 'resolved' or 'closed'. The existing dashboards have been left unchanged but should be revised to fully benefit from the new attribute (e.g. Open requests, Open problems, etc.) Note: the alpha version of the compiler had already been committed by mistake a few days ago. SVN:trunk[3859] --- application/cmdbabstract.class.inc.php | 10 +- core/attributedef.class.inc.php | 131 ++++++++++++++++-- core/dbobject.class.php | 11 +- core/metamodel.class.php | 108 ++++++++++++++- .../datamodel.itop-change-mgmt-itil.xml | 30 ++++ .../datamodel.itop-change-mgmt.xml | 6 + .../datamodel.itop-incident-mgmt-itil.xml | 6 + .../datamodel.itop-problem-mgmt.xml | 6 + .../datamodel.itop-request-mgmt-itil.xml | 6 + .../datamodel.itop-request-mgmt.xml | 6 + .../itop-tickets/datamodel.itop-tickets.xml | 33 ++++- .../2.x/itop-tickets/en.dict.itop-tickets.php | 6 + .../2.x/itop-tickets/fr.dict.itop-tickets.php | 6 + pages/UI.php | 4 +- setup/compiler.class.inc.php | 6 +- setup/itopdesignformat.class.inc.php | 11 +- 16 files changed, 353 insertions(+), 33 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 49d10c506..f2178c641 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1,5 +1,5 @@ $trash) { - $aDeps[$sAttCode] = MetaModel::GetPrequisiteAttributes($sClass, $sAttCode); + $aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); } $aList = $this->OrderDependentFields($aDeps); @@ -3489,7 +3489,7 @@ EOF $sFormPrefix = '2_'; foreach($aList as $sAttCode => $oAttDef) { - $aPrerequisites = MetaModel::GetPrequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one + $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one if (count($aPrerequisites) > 0) { // When 'enabling' a field, all its prerequisites must be enabled too diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 7f53dea14..529c11c11 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1,5 +1,5 @@ Get("allowed_values");} - public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} + public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");} public function GetDefaultValue($aArgs = array()) { // Note: so far, this feature is a prototype, @@ -1274,7 +1274,7 @@ class AttributeDBFieldVoid extends AttributeDefinition public function GetEditClass() {return "String";} public function GetValuesDef() {return $this->Get("allowed_values");} - public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} + public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");} public function IsDirectField() {return true;} public function IsScalar() {return true;} @@ -2992,6 +2992,120 @@ class AttributeEnum extends AttributeString } } +/** + * A meta enum is an aggregation of enum from subclasses into an enum of a base class + * It has been designed is to cope with the fact that statuses must be defined in leaf classes, while it makes sense to + * have a superstatus available on the root classe(s) + * + * @package iTopORM + */ +class AttributeMetaEnum extends AttributeEnum +{ + static public function ListExpectedParams() + { + return array('allowed_values', 'sql', 'default_value', 'mapping'); + } + + public function IsWritable() + { + return false; + } + + public function RequiresIndex() + { + return true; + } + + public function GetPrerequisiteAttributes($sClass = null) + { + if (is_null($sClass)) + { + $sClass = $this->GetHostClass(); + } + $aMappingData = $this->GetMapRule($sClass); + if ($aMappingData == null) + { + $aRet = array(); + } + else + { + $aRet = array($aMappingData['attcode']); + } + return $aRet; + } + + /** + * Overload the standard so as to leave the data unsorted + * + * @param array $aArgs + * @param string $sContains + * @return array|null + */ + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + $oValSetDef = $this->GetValuesDef(); + if (!$oValSetDef) return null; + $aRawValues = $oValSetDef->GetValueList(); + + if (is_null($aRawValues)) return null; + $aLocalizedValues = array(); + foreach ($aRawValues as $sKey => $sValue) + { + $aLocalizedValues[$sKey] = Str::pure2html($this->GetValueLabel($sKey)); + } + return $aLocalizedValues; + } + + /** + * Returns the meta value for the given object. + * See also MetaModel::RebuildMetaEnums() that must be maintained when MapValue changes + * + * @param $oObject + * @return mixed + * @throws Exception + */ + public function MapValue($oObject) + { + $aMappingData = $this->GetMapRule(get_class($oObject)); + if ($aMappingData == null) + { + $sRet = $this->GetDefaultValue(); + } + else + { + $sAttCode = $aMappingData['attcode']; + $value = $oObject->Get($sAttCode); + if (array_key_exists($value, $aMappingData['values'])) + { + $sRet = $aMappingData['values'][$value]; + } + elseif ($this->GetDefaultValue() != '') + { + $sRet = $this->GetDefaultValue(); + } + else + { + throw new Exception('AttributeMetaEnum::MapValue(): mapping not found for value "'.$value.'" in '.get_class($oObject).', on attribute '.MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()).'::'.$this->GetCode()); + } + } + return $sRet; + } + + public function GetMapRule($sClass) + { + $aMappings = $this->Get('mapping'); + if (array_key_exists($sClass, $aMappings)) + { + $aMappingData = $aMappings[$sClass]; + } + else + { + $sParent = MetaModel::GetParentClass($sClass); + $aMappingData = $this->GetMapRule($sParent); + } + return $aMappingData; + } +} /** * Map a date+time column to an attribute * @@ -3794,7 +3908,7 @@ class AttributeExternalField extends AttributeDefinition } } - public function GetPrerequisiteAttributes() + public function GetPrerequisiteAttributes($sClass = null) { return array($this->Get("extkey_attcode")); } @@ -4833,7 +4947,6 @@ class AttributeSubItem extends AttributeDefinition public function GetEditClass() {return "";} public function GetValuesDef() {return null;} - //public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} public function IsDirectField() {return true;} public function IsScalar() {return true;} @@ -5322,7 +5435,7 @@ class AttributeComputedFieldVoid extends AttributeDefinition public function GetEditClass() {return "";} public function GetValuesDef() {return null;} - public function GetPrerequisiteAttributes() {return $this->GetOptional("depends_on", array());} + public function GetPrerequisiteAttributes($sClass = null) {return $this->GetOptional("depends_on", array());} public function IsDirectField() {return true;} public function IsScalar() {return true;} @@ -5546,7 +5659,7 @@ class AttributeRedundancySettings extends AttributeDBField } public function GetValuesDef() {return null;} - public function GetPrerequisiteAttributes() {return array();} + public function GetPrerequisiteAttributes($sClass = null) {return array();} public function GetEditClass() {return "RedundancySetting";} protected function GetSQLCol($bFullSpec = false) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 50df3469c..1125ac131 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1,5 +1,5 @@ m_aCurrValues[$sAttCode] = $realvalue; $this->m_aTouchedAtt[$sAttCode] = true; unset($this->m_aModifiedAtt[$sAttCode]); - + + foreach (MetaModel::ListMetaAttributes(get_class($this), $sAttCode) as $sMetaAttCode => $oMetaAttDef) + { + $this->Set($sMetaAttCode, $oMetaAttDef->MapValue($this)); + } + // The object has changed, reset caches $this->m_bCheckStatus = null; diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 8aa9131d4..5567bd4a9 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1,5 +1,5 @@ GetPrerequisiteAttributes(); } /** - * Find all attributes that depend on the specified one (reverse of GetPrequisiteAttributes) + * Find all attributes that depend on the specified one (reverse of GetPrerequisiteAttributes) * @param string $sClass Name of the class * @param string $sAttCode Code of the attributes * @return Array List of attribute codes that depend on the given attribute, empty array if none. @@ -510,7 +510,7 @@ abstract class MetaModel self::_check_subclass($sClass); foreach (self::ListAttributeDefs($sClass) as $sDependentAttCode=>$void) { - $aPrerequisites = self::GetPrequisiteAttributes($sClass, $sDependentAttCode); + $aPrerequisites = self::GetPrerequisiteAttributes($sClass, $sDependentAttCode); if (in_array($sAttCode, $aPrerequisites)) { $aResults[] = $sDependentAttCode; @@ -633,7 +633,8 @@ abstract class MetaModel private static $m_aAttribDefs = array(); // array of ("classname" => array of attributes) private static $m_aAttribOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) private static $m_aExtKeyFriends = array(); // array of ("classname" => array of ("indirect ext key attcode"=> array of ("relative ext field"))) - private static $m_aIgnoredAttributes = array(); //array of ("classname" => array of ("attcode") + private static $m_aIgnoredAttributes = array(); //array of ("classname" => array of ("attcode")) + private static $m_aEnumToMeta = array(); // array of ("classname" => array of ("attcode" => array of ("metaattcode" => oMetaAttDef)) final static public function ListAttributeDefs($sClass) { @@ -848,6 +849,18 @@ abstract class MetaModel return self::$m_aTrackForwardCache[$sClass]; } + final static public function ListMetaAttributes($sClass, $sAttCode) + { + if (isset(self::$m_aEnumToMeta[$sClass][$sAttCode])) + { + $aRet = self::$m_aEnumToMeta[$sClass][$sAttCode]; + } + else + { + $aRet = array(); + } + return $aRet; + } /** * Get the attribute label @@ -1847,6 +1860,15 @@ abstract class MetaModel } } } + if ($oAttDef instanceof AttributeMetaEnum) + { + $aMappingData = $oAttDef->GetMapRule($sClass); + if ($aMappingData != null) + { + $sEnumAttCode = $aMappingData['attcode']; + self::$m_aEnumToMeta[$sClass][$sEnumAttCode][$sAttCode] = $oAttDef; + } + } } // Add a 'id' filter @@ -2685,7 +2707,77 @@ abstract class MetaModel CMDBSource::Query($sSQL); } } - + + /** + * Update the meta enums + * See Also AttributeMetaEnum::MapValue that must be aligned with the above implementation + * + * @param $bVerbose boolean Displays some information about what is done/what needs to be done + */ + public static function RebuildMetaEnums($bVerbose = false) + { + foreach (self::GetClasses() as $sClass) + { + if (!self::HasTable($sClass)) continue; + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // Check (once) all the attributes that are hierarchical keys + if((self::GetAttributeOrigin($sClass, $sAttCode) == $sClass) && $oAttDef instanceof AttributeEnum) + { + if (isset(self::$m_aEnumToMeta[$sClass][$sAttCode])) + { + foreach (self::$m_aEnumToMeta[$sClass][$sAttCode] as $sMetaAttCode => $oMetaAttDef) + { + $aMetaValues = array(); // array of (metavalue => array of values) + foreach ($oAttDef->GetAllowedValues() as $sCode => $sLabel) + { + $aMappingData = $oMetaAttDef->GetMapRule($sClass); + if ($aMappingData == null) + { + $sMetaValue = $oMetaAttDef->GetDefaultValue(); + } + else + { + if (array_key_exists($sCode, $aMappingData['values'])) + { + $sMetaValue = $aMappingData['values'][$sCode]; + } + elseif ($oMetaAttDef->GetDefaultValue() != '') + { + $sMetaValue = $oMetaAttDef->GetDefaultValue(); + } + else + { + throw new Exception('MetaModel::RebuildMetaEnums(): mapping not found for value "'.$sCode.'"" in '.$sClass.', on attribute '.self::GetAttributeOrigin($sClass, $oMetaAttDef->GetCode()).'::'.$oMetaAttDef->GetCode()); + } + } + $aMetaValues[$sMetaValue][] = $sCode; + } + foreach ($aMetaValues as $sMetaValue => $aEnumValues) + { + $sMetaTable = self::DBGetTable($sClass, $sMetaAttCode); + $sEnumTable = self::DBGetTable($sClass); + $aColumns = array_keys($oMetaAttDef->GetSQLColumns()); + $sMetaColumn = reset($aColumns); + $aColumns = array_keys($oAttDef->GetSQLColumns()); + $sEnumColumn = reset($aColumns); + $sValueList = implode(', ', CMDBSource::Quote($aEnumValues)); + $sSql = "UPDATE `$sMetaTable` JOIN `$sEnumTable` ON `$sEnumTable`.id = `$sMetaTable`.id SET `$sMetaTable`.`$sMetaColumn` = '$sMetaValue' WHERE `$sEnumTable`.`$sEnumColumn` IN ($sValueList) AND `$sMetaTable`.`$sMetaColumn` != '$sMetaValue'"; + if ($bVerbose) + { + echo "Executing query: $sSql\n"; + } + CMDBSource::Query($sSql); + } + } + } + } + } + } + } + + public static function CheckDataSources($bDiagnostics, $bVerbose) { $sOQL = 'SELECT SynchroDataSource'; @@ -4190,6 +4282,7 @@ abstract class MetaModel self::$m_aStimuli = $result['m_aStimuli']; self::$m_aTransitions = $result['m_aTransitions']; self::$m_aHighlightScales = $result['m_aHighlightScales']; + self::$m_aEnumToMeta = $result['m_aEnumToMeta']; } $oKPI->ComputeAndReport('Metamodel APC (fetch + read)'); } @@ -4227,6 +4320,7 @@ abstract class MetaModel $aCache['m_aStimuli'] = self::$m_aStimuli; // array of ("classname" => array of ("stimuluscode"=>array('label'=>...))) $aCache['m_aTransitions'] = self::$m_aTransitions; // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD))) $aCache['m_aHighlightScales'] = self::$m_aHighlightScales; // array of ("classname" => array of higlightcodes))) + $aCache['m_aEnumToMeta'] = self::$m_aEnumToMeta; apc_store($sOqlAPCCacheId, $aCache); $oKPI->ComputeAndReport('Metamodel APC (store)'); } diff --git a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml index 54ed7d3ac..8d225e7fa 100755 --- a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml +++ b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml @@ -1103,6 +1103,9 @@ 30 + + 35 + 40 @@ -1179,6 +1182,9 @@ 60 + + 65 + 70 @@ -1893,6 +1899,9 @@ 30 + + 35 + 40 @@ -1969,6 +1978,9 @@ 60 + + 65 + 70 @@ -2884,6 +2896,9 @@ 30 + + 35 + 40 @@ -2963,6 +2978,9 @@ 60 + + 65 + 70 @@ -3570,6 +3588,9 @@ 30 + + 35 + 40 @@ -3652,6 +3673,9 @@ 60 + + 65 + 70 @@ -4323,6 +4347,9 @@ 30 + + 35 + 40 @@ -4402,6 +4429,9 @@ 60 + + 65 + 70 diff --git a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml index 4f6830f9d..302870fe0 100755 --- a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml +++ b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml @@ -664,6 +664,9 @@ 40 + + 45 + 50 @@ -707,6 +710,9 @@ 50 + + 55 + 60 diff --git a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml index 518c8167d..6e8448c0a 100755 --- a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml +++ b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml @@ -1553,6 +1553,9 @@ 90 + + 95 + 100 @@ -1614,6 +1617,9 @@ 50 + + 55 + 60 diff --git a/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml b/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml index daa76b917..293187964 100755 --- a/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml +++ b/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml @@ -607,6 +607,9 @@ 50 + + 55 + 60 @@ -653,6 +656,9 @@ 40 + + 45 + 50 diff --git a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml index e542f87c8..6fb81e76d 100755 --- a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml +++ b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml @@ -1615,6 +1615,9 @@ 90 + + 95 + 100 @@ -1679,6 +1682,9 @@ 50 + + 55 + 60 diff --git a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml index 9916b3c6d..76aa59c32 100755 --- a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml +++ b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml @@ -1615,6 +1615,9 @@ 90 + + 95 + 100 @@ -1679,6 +1682,9 @@ 50 + + 55 + 60 diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index 4456ca5fa..277591027 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -1,5 +1,5 @@ - + service_id AND sc.org_id = :this->org_id AND slt.request_type = :request_type AND slt.priority = :this->priority]]> @@ -45,6 +45,28 @@ + + + active + inactive + + operational_status + active + + + status + + + + + + + + + + + + ref @@ -238,6 +260,9 @@ 70 + + 75 + 80 @@ -275,6 +300,9 @@ 30 + + 35 + 40 @@ -309,6 +337,9 @@ 60 + + 65 + 70 diff --git a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php index 6888d212c..169e79153 100755 --- a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php @@ -82,6 +82,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Ticket/Attribute:workorders_list+' => 'All the work orders for this ticket', 'Class:Ticket/Attribute:finalclass' => 'Type', 'Class:Ticket/Attribute:finalclass+' => '', + 'Class:Ticket/Attribute:operational_status' => 'Operational status', + 'Class:Ticket/Attribute:operational_status+' => 'Computed after the detailed status', + 'Class:Ticket/Attribute:operational_status/Value:active' => 'Active', + 'Class:Ticket/Attribute:operational_status/Value:active+' => 'Work in progress', + 'Class:Ticket/Attribute:operational_status/Value:inactive' => 'Inactive', + 'Class:Ticket/Attribute:operational_status/Value:inactive+' => 'Done', 'Ticket:ImpactAnalysis' => 'Impact Analysis', )); diff --git a/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php index 893d8ab32..23c5b68f3 100755 --- a/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php @@ -69,6 +69,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:Ticket/Attribute:workorders_list+' => '', 'Class:Ticket/Attribute:finalclass' => 'Type', 'Class:Ticket/Attribute:finalclass+' => '', + 'Class:Ticket/Attribute:operational_status' => 'Statut opérationnel', + 'Class:Ticket/Attribute:operational_status+' => 'Calculé à partir du statut détaillé', + 'Class:Ticket/Attribute:operational_status/Value:active' => 'Actif', + 'Class:Ticket/Attribute:operational_status/Value:active+' => 'Traitement en cours', + 'Class:Ticket/Attribute:operational_status/Value:inactive' => 'Inactif', + 'Class:Ticket/Attribute:operational_status/Value:inactive+' => 'Ticket définitivement fermé', 'Ticket:ImpactAnalysis' => 'Analyse d\'Impact', )); diff --git a/pages/UI.php b/pages/UI.php index 1821b9aa3..e0e42b6ef 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1,5 +1,5 @@ 0) { // When 'enabling' a field, all its prerequisites must be enabled too diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 869b46672..b75700d54 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -1,5 +1,5 @@ GetPropString($oField, 'display_style', 'list'); $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql', ''); $aParameters['default_value'] = $this->GetPropString($oField, 'default_value', ''); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; $oMappings = $oField->GetUniqueElement('mappings'); $oMappingNodes = $oMappings->getElementsByTagName('mapping'); diff --git a/setup/itopdesignformat.class.inc.php b/setup/itopdesignformat.class.inc.php index b1b824e3c..4e0fb002e 100644 --- a/setup/itopdesignformat.class.inc.php +++ b/setup/itopdesignformat.class.inc.php @@ -1,5 +1,5 @@ LogWarning('The module design defined in '.self::GetItopNodePath($oNode).' will be lost.'); $this->DeleteNode($oNode); } + + // Remove MetaEnum attributes + // + $oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeMetaEnum']"); + foreach ($oNodeList as $oNode) + { + $this->LogWarning('The attribute '.self::GetItopNodePath($oNode).' is irrelevant and must be removed.'); + $this->DeleteNode($oNode); + } } /**