diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 851904e66..0acd3b1e1 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -589,7 +589,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add("<$sClassName alias=\"$sAlias\" id=\"".$oObj->GetKey()."\">\n"); foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) { - if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar()) && ($sAttCode != 'finalclass') ) + if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar())) { $sValue = $oObj->GetAsXML($sAttCode); $oPage->add("<$sAttCode>$sValue\n"); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 1a7bdb7b6..0556d72e2 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -59,7 +59,7 @@ abstract class AttributeDefinition protected $m_sCode; private $m_aParams = array(); - private $m_sHostClass = array(); + private $m_sHostClass = '!undefined!'; protected function Get($sParamName) {return $this->m_aParams[$sParamName];} protected function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));} @@ -132,7 +132,7 @@ abstract class AttributeDefinition public function IsNullAllowed() {return true;} public function GetNullValue() {return null;} public function GetCode() {return $this->m_sCode;} - public function GetLabel() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $this->m_sCode);} + public function GetLabel() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $this->m_sCode);} public function GetLabel_Obsolete() { // Written for compatibility with a data model written prior to version 0.9.1 @@ -668,6 +668,36 @@ class AttributeClass extends AttributeString } } +/** + * The attribute dedicated to the finalclass automatic attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeFinalClass extends AttributeString +{ + public function __construct($sCode, $aParams) + { + $this->m_sCode = $sCode; + $aParams["allowed_values"] = null; + parent::__construct($sCode, $aParams); + } + + public function IsWritable() + { + return false; + } + + public function GetAsHTML($sValue) + { + return MetaModel::GetName($sValue); + } +} + /** * Map a varchar column (size < ?) to an attribute that must never be shown to the user * diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 3a5422d9b..c9957c196 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -354,18 +354,18 @@ abstract class MetaModel self::_check_subclass($sClass); return self::$m_aClassParams[$sClass]["db_finalclass_field"]; } - final static public function HasFinalClassField($sClass) - { - self::_check_subclass($sClass); - if (!array_key_exists("db_finalclass_field", self::$m_aClassParams[$sClass])) return false; - return (self::$m_aClassParams[$sClass]["db_finalclass_field"]); - } final static public function IsStandaloneClass($sClass) { self::_check_subclass($sClass); - $sRootClass = self::GetRootClass($sClass); - return (!self::HasFinalClassField($sRootClass)); + if (count(self::$m_aChildClasses[$sClass]) == 0) + { + if (count(self::$m_aParentClasses[$sClass]) == 0) + { + return true; + } + } + return false; } final static public function IsParentClass($sParentClass, $sChildClass) { @@ -877,37 +877,6 @@ abstract class MetaModel self::$m_aFilterDefs[$sClass]['id'] = $oFilter; self::$m_aFilterOrigins[$sClass]['id'] = $sClass; - // Add a 'class' attribute/filter to the root classes and their children - // - if (!self::IsStandaloneClass($sClass)) - { - if (array_key_exists('finalclass', self::$m_aAttribDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code"); - } - if (array_key_exists('finalclass', self::$m_aFilterDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code"); - } - $sClassAttCode = 'finalclass'; - $sRootClass = self::GetRootClass($sClass); - $sDbFinalClassField = self::DBGetClassField($sRootClass); - $oClassAtt = new AttributeClass($sClassAttCode, array( - "class_category"=>null, - "more_values"=>'', - "sql"=>$sDbFinalClassField, - "default_value"=>$sClass, - "is_null_allowed"=>false, - "depends_on"=>array() - )); - self::$m_aAttribDefs[$sClass][$sClassAttCode] = $oClassAtt; - self::$m_aAttribOrigins[$sClass][$sClassAttCode] = $sRootClass; - - $oClassFlt = new FilterFromAttribute($oClassAtt); - self::$m_aFilterDefs[$sClass][$sClassAttCode] = $oClassFlt; - self::$m_aFilterOrigins[$sClass][$sClassAttCode] = self::GetRootClass($sClass); - } - // Define defaults values for the standard ZLists // //foreach (self::$m_aListInfos as $sListCode => $aListConfig) @@ -921,6 +890,50 @@ abstract class MetaModel //} } + // Add a 'class' attribute/filter to the root classes and their children + // + foreach(self::EnumRootClasses() as $sRootClass) + { + if (self::IsStandaloneClass($sRootClass)) continue; + + $sDbFinalClassField = self::DBGetClassField($sRootClass); + if (strlen($sDbFinalClassField) == 0) + { + $sDbFinalClassField = 'finalclass'; + self::$m_aClassParams[$sClass]["db_finalclass_field"] = 'finalclass'; + } + $oClassAtt = new AttributeFinalClass('finalclass', array( + "sql"=>$sDbFinalClassField, + "default_value"=>$sRootClass, + "is_null_allowed"=>false, + "depends_on"=>array() + )); + $oClassAtt->SetHostClass($sRootClass); + self::$m_aAttribDefs[$sRootClass]['finalclass'] = $oClassAtt; + self::$m_aAttribOrigins[$sRootClass]['finalclass'] = $sRootClass; + + $oClassFlt = new FilterFromAttribute($oClassAtt); + self::$m_aFilterDefs[$sRootClass]['finalclass'] = $oClassFlt; + self::$m_aFilterOrigins[$sRootClass]['finalclass'] = $sRootClass; + + foreach(self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_EXCLUDETOP) as $sChildClass) + { + if (array_key_exists('finalclass', self::$m_aAttribDefs[$sChildClass])) + { + throw new CoreException("Class $sChildClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code"); + } + if (array_key_exists('finalclass', self::$m_aFilterDefs[$sChildClass])) + { + throw new CoreException("Class $sChildClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code"); + } + self::$m_aAttribDefs[$sChildClass]['finalclass'] = clone $oClassAtt; + self::$m_aAttribOrigins[$sChildClass]['finalclass'] = $sRootClass; + + $oClassFlt = new FilterFromAttribute($oClassAtt); + self::$m_aFilterDefs[$sChildClass]['finalclass'] = $oClassFlt; + self::$m_aFilterOrigins[$sChildClass]['finalclass'] = self::GetRootClass($sChildClass); + } + } } // To be overriden, must be called for any object class (optimization) @@ -1014,18 +1027,17 @@ abstract class MetaModel self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); } - // later on, we might consider inheritance in different ways !!! - //if (strlen(self::DBGetTable($sSourceClass)) != 0) - if (self::HasFinalClassField(self::$m_aRootClasses[$sSourceClass])) + // Build root class information + if (array_key_exists($sSourceClass, self::$m_aRootClasses)) { - // Inherit the root class + // Inherit... self::$m_aRootClasses[$sTargetClass] = self::$m_aRootClasses[$sSourceClass]; } else { - // I am a root class, standalone as well ! - // ???? - //self::$m_aRootClasses[$sTargetClass] = $sTargetClass; + // This class will be the root class + self::$m_aRootClasses[$sSourceClass] = $sSourceClass; + self::$m_aRootClasses[$sTargetClass] = $sSourceClass; } self::$m_aParentClasses[$sTargetClass] += self::$m_aParentClasses[$sSourceClass]; self::$m_aParentClasses[$sTargetClass][] = $sSourceClass; @@ -1212,6 +1224,10 @@ abstract class MetaModel self::_check_subclass($sClass); return (self::GetRootClass($sClass) == $sClass); } + public static function EnumRootClasses() + { + return array_unique(self::$m_aRootClasses); + } public static function EnumParentClasses($sClass) { self::_check_subclass($sClass); @@ -2540,41 +2556,41 @@ abstract class MetaModel $sTable = self::DBGetTable($sClass); $sKeyField = self::DBGetKey($sClass); - // Check that the final class field contains the name of a class which inherited from the current class - // - if (self::HasFinalClassField($sClass)) + if (!self::IsStandaloneClass($sClass)) { - $sFinalClassField = self::DBGetClassField($sClass); - - $aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); - $sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true)); - - $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)"; - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `$sFinalClassField`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - - // Compound objects model - node/leaf classes (not the root itself) - // - if (!self::IsStandaloneClass($sClass) && !self::HasFinalClassField($sClass)) - { - $sRootTable = self::DBGetTable($sRootClass); - $sRootKey = self::DBGetKey($sRootClass); - $sFinalClassField = self::DBGetClassField($sRootClass); - - $aExpectedClasses = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); - $sExpectedClasses = implode(",", CMDBSource::Quote($aExpectedClasses, true)); - - // Check that any record found here has its counterpart in the root table - // and which refers to a child class - // - $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` as maintable LEFT JOIN `$sRootTable` ON maintable.`$sKeyField` = `$sRootTable`.`$sRootKey` AND `$sRootTable`.`$sFinalClassField` IN ($sExpectedClasses) WHERE `$sRootTable`.`$sRootKey` IS NULL"; - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in `$sTable`, but no counterpart in root table `$sRootTable` (inc. records pointing to a class in {".$sExpectedClasses."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - - // Check that any record found in the root table and referring to a child class - // has its counterpart here (detect orphan nodes -root or in the middle of the hierarchy) - // - $sSelWrongRecs = "SELECT DISTINCT maintable.`$sRootKey` AS id FROM `$sRootTable` AS maintable LEFT JOIN `$sTable` ON maintable.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sKeyField` IS NULL AND maintable.`$sFinalClassField` IN ($sExpectedClasses)"; - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in root table `$sRootTable`, but no counterpart in table `$sTable` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + if (self::IsRootClass($sClass)) + { + // Check that the final class field contains the name of a class which inherited from the current class + // + $sFinalClassField = self::DBGetClassField($sClass); + + $aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); + $sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true)); + + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `$sFinalClassField`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + else + { + $sRootTable = self::DBGetTable($sRootClass); + $sRootKey = self::DBGetKey($sRootClass); + $sFinalClassField = self::DBGetClassField($sRootClass); + + $aExpectedClasses = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); + $sExpectedClasses = implode(",", CMDBSource::Quote($aExpectedClasses, true)); + + // Check that any record found here has its counterpart in the root table + // and which refers to a child class + // + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` as maintable LEFT JOIN `$sRootTable` ON maintable.`$sKeyField` = `$sRootTable`.`$sRootKey` AND `$sRootTable`.`$sFinalClassField` IN ($sExpectedClasses) WHERE `$sRootTable`.`$sRootKey` IS NULL"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in `$sTable`, but no counterpart in root table `$sRootTable` (inc. records pointing to a class in {".$sExpectedClasses."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + + // Check that any record found in the root table and referring to a child class + // has its counterpart here (detect orphan nodes -root or in the middle of the hierarchy) + // + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sRootKey` AS id FROM `$sRootTable` AS maintable LEFT JOIN `$sTable` ON maintable.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sKeyField` IS NULL AND maintable.`$sFinalClassField` IN ($sExpectedClasses)"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in root table `$sRootTable`, but no counterpart in table `$sTable` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } } foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index 67c50d1cb..4ee8ac798 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -35,7 +35,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:CMDBChangeOp/Attribute:objclass+' => 'object class', 'Class:CMDBChangeOp/Attribute:objkey' => 'object id', 'Class:CMDBChangeOp/Attribute:objkey+' => 'object id', - 'Class:CMDBChangeOp/Attribute:finalclass' => 'finalclass', + 'Class:CMDBChangeOp/Attribute:finalclass' => 'type', 'Class:CMDBChangeOp/Attribute:finalclass+' => '', )); @@ -116,7 +116,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Event/Attribute:date+' => 'date and time at which the changes have been recorded', 'Class:Event/Attribute:userinfo' => 'user info', 'Class:Event/Attribute:userinfo+' => 'identification of the user that was doing the action that triggered this event', - 'Class:Event/Attribute:finalclass' => 'finalclass', + 'Class:Event/Attribute:finalclass' => 'type', 'Class:Event/Attribute:finalclass+' => '', )); @@ -221,7 +221,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Action/Attribute:status/Value:disabled+' => 'Inactive', 'Class:Action/Attribute:related_triggers' => 'Related Triggers', 'Class:Action/Attribute:related_triggers+' => 'Triggers linked to this action', - 'Class:Action/Attribute:finalclass' => 'finalclass', + 'Class:Action/Attribute:finalclass' => 'Type', 'Class:Action/Attribute:finalclass+' => '', )); @@ -278,7 +278,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Trigger/Attribute:description+' => 'one line description', 'Class:Trigger/Attribute:linked_actions' => 'Triggered actions', 'Class:Trigger/Attribute:linked_actions+' => 'Actions performed when the trigger is activated', - 'Class:Trigger/Attribute:finalclass' => 'finalclass', + 'Class:Trigger/Attribute:finalclass' => 'Type', 'Class:Trigger/Attribute:finalclass+' => '', )); diff --git a/dictionaries/dictionary.itop.model.php b/dictionaries/dictionary.itop.model.php index 5a7648fd0..ca4594ca9 100644 --- a/dictionaries/dictionary.itop.model.php +++ b/dictionaries/dictionary.itop.model.php @@ -67,7 +67,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:logRealObject/Attribute:org_id+' => 'ID of the object owner organization', 'Class:logRealObject/Attribute:org_name' => 'Organization', 'Class:logRealObject/Attribute:org_name+' => 'Company / Department owning this object', - 'Class:logRealObject/Attribute:finalclass' => 'finalclass', + 'Class:logRealObject/Attribute:finalclass' => 'Type', 'Class:logRealObject/Attribute:finalclass+' => '', )); diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index d0c193348..b9b839c92 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -29,7 +29,7 @@ function GetMappingForField($sClassName, $sFieldName, $iFieldIndex) } } } - else if ($oAttDef->IsWritable() && ($sAttCode != 'finalclass')) // finalclass should not be considered as 'writable' isn't it ? + else if ($oAttDef->IsWritable()) { $aChoices[$sAttCode] = $oAttDef->GetLabel(); } diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index aff8fa18a..b9f685141 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -156,7 +156,7 @@ class XMLDataLoader $oTargetObj = MetaModel::NewObject($sClass); foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) { - if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar()) && ($sAttCode != 'finalclass') ) + if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar())) { if ($oAttDef->IsExternalKey()) {