$sClass: $sListCode (".count($aAllAttributes)." attributes)
\n"; // } //} } } // To be overriden, must be called for any object class (optimization) public static function Init() { // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) } // To be overloaded by biz model declarations public static function GetRelationQueries($sRelCode) { // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) return array(); } public static function Init_Params($aParams) { // Check mandatory params $aMandatParams = array( "category" => "group classes by modules defining their visibility in the UI", "key_type" => "autoincrement | string", "name_attcode" => "define wich attribute is the class name, may be an array of attributes (format specified in the dictionary as 'Class:myclass/Name' => '%1\$s %2\$s...'", "state_attcode" => "define wich attribute is representing the state (object lifecycle)", "reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes", "db_table" => "database table", "db_key_field" => "database field which is the key", "db_finalclass_field" => "database field wich is the reference to the actual class of the object, considering that this will be a compound class", ); $sClass = self::GetCallersPHPClass("Init", self::$m_bTraceSourceFiles); foreach($aMandatParams as $sParamName=>$sParamDesc) { if (!array_key_exists($sParamName, $aParams)) { throw new CoreException("Declaration of class $sClass - missing parameter $sParamName"); } } $aCategories = explode(',', $aParams['category']); foreach ($aCategories as $sCategory) { self::$m_Category2Class[$sCategory][] = $sClass; } self::$m_Category2Class[''][] = $sClass; // all categories, include this one self::$m_aRootClasses[$sClass] = $sClass; // first, let consider that I am the root... updated on inheritance self::$m_aParentClasses[$sClass] = array(); self::$m_aChildClasses[$sClass] = array(); self::$m_aClassParams[$sClass]= $aParams; self::$m_aAttribDefs[$sClass] = array(); self::$m_aAttribOrigins[$sClass] = array(); self::$m_aExtKeyFriends[$sClass] = array(); self::$m_aFilterDefs[$sClass] = array(); self::$m_aFilterOrigins[$sClass] = array(); } protected static function object_array_mergeclone($aSource1, $aSource2) { $aRes = array(); foreach ($aSource1 as $key=>$object) { $aRes[$key] = clone $object; } foreach ($aSource2 as $key=>$object) { $aRes[$key] = clone $object; } return $aRes; } public static function Init_InheritAttributes($sSourceClass = null) { $sTargetClass = self::GetCallersPHPClass("Init"); if (empty($sSourceClass)) { // Default: inherit from parent class $sSourceClass = self::GetParentPersistentClass($sTargetClass); if (empty($sSourceClass)) return; // no attributes for the mother of all classes } if (isset(self::$m_aAttribDefs[$sSourceClass])) { if (!isset(self::$m_aAttribDefs[$sTargetClass])) { self::$m_aAttribDefs[$sTargetClass] = array(); self::$m_aAttribOrigins[$sTargetClass] = array(); } self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); foreach(self::$m_aAttribDefs[$sTargetClass] as $sAttCode => $oAttDef) { $oAttDef->SetHostClass($sTargetClass); } self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); } // Build root class information if (array_key_exists($sSourceClass, self::$m_aRootClasses)) { // Inherit... self::$m_aRootClasses[$sTargetClass] = self::$m_aRootClasses[$sSourceClass]; } else { // 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; // I am the child of each and every parent... foreach(self::$m_aParentClasses[$sTargetClass] as $sAncestorClass) { self::$m_aChildClasses[$sAncestorClass][] = $sTargetClass; } } protected static function Init_IsKnownClass($sClass) { // Differs from self::IsValidClass() // because it is being called before all the classes have been initialized if (!class_exists($sClass)) return false; if (!is_subclass_of($sClass, 'DBObject')) return false; return true; } public static function Init_AddAttribute(AttributeDefinition $oAtt, $sTargetClass = null) { if (!$sTargetClass) { $sTargetClass = self::GetCallersPHPClass("Init"); } $sAttCode = $oAtt->GetCode(); if ($sAttCode == 'finalclass') { throw new Exception("Declaration of $sTargetClass: using the reserved keyword '$sAttCode' in attribute declaration"); } if ($sAttCode == 'friendlyname') { throw new Exception("Declaration of $sTargetClass: using the reserved keyword '$sAttCode' in attribute declaration"); } if (array_key_exists($sAttCode, self::$m_aAttribDefs[$sTargetClass])) { throw new Exception("Declaration of $sTargetClass: attempting to redeclare the inherited attribute '$sAttCode', originaly declared in ".self::$m_aAttribOrigins[$sTargetClass][$sAttCode]); } // Set the "host class" as soon as possible, since HierarchicalKeys use it for their 'target class' as well // and this needs to be know early (for Init_IsKnowClass 19 lines below) $oAtt->SetHostClass($sTargetClass); // Some attributes could refer to a class // declared in a module which is currently not installed/active // We simply discard those attributes // if ($oAtt->IsLinkSet()) { $sRemoteClass = $oAtt->GetLinkedClass(); if (!self::Init_IsKnownClass($sRemoteClass)) { self::$m_aIgnoredAttributes[$sTargetClass][$oAtt->GetCode()] = $sRemoteClass; return; } } elseif($oAtt->IsExternalKey()) { $sRemoteClass = $oAtt->GetTargetClass(); if (!self::Init_IsKnownClass($sRemoteClass)) { self::$m_aIgnoredAttributes[$sTargetClass][$oAtt->GetCode()] = $sRemoteClass; return; } } elseif($oAtt->IsExternalField()) { $sExtKeyAttCode = $oAtt->GetKeyAttCode(); if (isset(self::$m_aIgnoredAttributes[$sTargetClass][$sExtKeyAttCode])) { // The corresponding external key has already been ignored self::$m_aIgnoredAttributes[$sTargetClass][$oAtt->GetCode()] = self::$m_aIgnoredAttributes[$sTargetClass][$sExtKeyAttCode]; return; } // #@# todo - Check if the target attribute is still there // this is not simple to implement because is involves // several passes (the load order has a significant influence on that) } self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt; self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass; // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used } public static function Init_SetZListItems($sListCode, $aItems, $sTargetClass = null) { MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos); if (!$sTargetClass) { $sTargetClass = self::GetCallersPHPClass("Init"); } // Discard attributes that do not make sense // (missing classes in the current module combination, resulting in irrelevant ext key or link set) // self::Init_CheckZListItems($aItems, $sTargetClass); self::$m_aListData[$sTargetClass][$sListCode] = $aItems; } protected static function Init_CheckZListItems(&$aItems, $sTargetClass) { foreach($aItems as $iFoo => $attCode) { if (is_array($attCode)) { // Note: to make sure that the values will be updated recursively, // do not pass $attCode, but $aItems[$iFoo] instead self::Init_CheckZListItems($aItems[$iFoo], $sTargetClass); if (count($aItems[$iFoo]) == 0) { unset($aItems[$iFoo]); } } else if (isset(self::$m_aIgnoredAttributes[$sTargetClass][$attCode])) { unset($aItems[$iFoo]); } } } public static function FlattenZList($aList) { $aResult = array(); foreach($aList as $value) { if (!is_array($value)) { $aResult[] = $value; } else { $aResult = array_merge($aResult, self::FlattenZList($value)); } } return $aResult; } public static function Init_DefineState($sStateCode, $aStateDef) { $sTargetClass = self::GetCallersPHPClass("Init"); if (is_null($aStateDef['attribute_list'])) $aStateDef['attribute_list'] = array(); $sParentState = $aStateDef['attribute_inherit']; if (!empty($sParentState)) { // Inherit from the given state (must be defined !) // $aToInherit = self::$m_aStates[$sTargetClass][$sParentState]; // Reset the constraint when it was mandatory to set the value at the previous state // foreach ($aToInherit['attribute_list'] as $sState => $iFlags) { $iFlags = $iFlags & ~OPT_ATT_MUSTPROMPT; $iFlags = $iFlags & ~OPT_ATT_MUSTCHANGE; $aToInherit['attribute_list'][$sState] = $iFlags; } // The inherited configuration could be overriden $aStateDef['attribute_list'] = array_merge($aToInherit['attribute_list'], $aStateDef['attribute_list']); } foreach($aStateDef['attribute_list'] as $sAttCode => $iFlags) { if (isset(self::$m_aIgnoredAttributes[$sTargetClass][$sAttCode])) { unset($aStateDef['attribute_list'][$sAttCode]); } } self::$m_aStates[$sTargetClass][$sStateCode] = $aStateDef; // by default, create an empty set of transitions associated to that state self::$m_aTransitions[$sTargetClass][$sStateCode] = array(); } public static function Init_DefineHighlightScale($aHighlightScale) { $sTargetClass = self::GetCallersPHPClass("Init"); self::$m_aHighlightScales[$sTargetClass] = $aHighlightScale; } public static function GetHighlightScale($sTargetClass) { $aScale = array(); $aParentScale = array(); $sParentClass = self::GetParentPersistentClass($sTargetClass); if (!empty($sParentClass)) { // inherit the scale from the parent class $aParentScale = self::GetHighlightScale($sParentClass); } if (array_key_exists($sTargetClass, self::$m_aHighlightScales)) { $aScale = self::$m_aHighlightScales[$sTargetClass]; } return array_merge($aParentScale, $aScale); // Merge both arrays, the values from the last one have precedence } public static function GetHighlightCode($sTargetClass, $sStateCode) { $sCode = ''; if ( array_key_exists($sTargetClass, self::$m_aStates) && array_key_exists($sStateCode, self::$m_aStates[$sTargetClass]) && array_key_exists('highlight', self::$m_aStates[$sTargetClass][$sStateCode]) ) { $sCode = self::$m_aStates[$sTargetClass][$sStateCode]['highlight']['code']; } else { // Check the parent's definition $sParentClass = self::GetParentPersistentClass($sTargetClass); if (!empty($sParentClass)) { $sCode = self::GetHighlightCode($sParentClass, $sStateCode); } } return $sCode; } public static function Init_OverloadStateAttribute($sStateCode, $sAttCode, $iFlags) { // Warning: this is not sufficient: the flags have to be copied to the states that are inheriting from this state $sTargetClass = self::GetCallersPHPClass("Init"); self::$m_aStates[$sTargetClass][$sStateCode]['attribute_list'][$sAttCode] = $iFlags; } public static function Init_DefineStimulus($oStimulus) { $sTargetClass = self::GetCallersPHPClass("Init"); self::$m_aStimuli[$sTargetClass][$oStimulus->GetCode()] = $oStimulus; // I wanted to simplify the syntax of the declaration of objects in the biz model // Therefore, the reference to the host class is set there $oStimulus->SetHostClass($sTargetClass); } public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef) { $sTargetClass = self::GetCallersPHPClass("Init"); if (is_null($aTransitionDef['actions'])) $aTransitionDef['actions'] = array(); self::$m_aTransitions[$sTargetClass][$sStateCode][$sStimulusCode] = $aTransitionDef; } public static function Init_InheritLifecycle($sSourceClass = '') { $sTargetClass = self::GetCallersPHPClass("Init"); if (empty($sSourceClass)) { // Default: inherit from parent class $sSourceClass = self::GetParentPersistentClass($sTargetClass); if (empty($sSourceClass)) return; // no attributes for the mother of all classes } self::$m_aClassParams[$sTargetClass]["state_attcode"] = self::$m_aClassParams[$sSourceClass]["state_attcode"]; self::$m_aStates[$sTargetClass] = self::$m_aStates[$sSourceClass]; // #@# Note: the aim is to clone the data, could be an issue if the simuli objects are changed self::$m_aStimuli[$sTargetClass] = self::$m_aStimuli[$sSourceClass]; self::$m_aTransitions[$sTargetClass] = self::$m_aTransitions[$sSourceClass]; } // // Static API // public static function GetRootClass($sClass = null) { self::_check_subclass($sClass); return self::$m_aRootClasses[$sClass]; } public static function IsRootClass($sClass) { self::_check_subclass($sClass); return (self::GetRootClass($sClass) == $sClass); } public static function GetParentClass($sClass) { if (count(self::$m_aParentClasses[$sClass]) == 0) { return null; } else { return end(self::$m_aParentClasses[$sClass]); } } public static function GetLowestCommonAncestor($aClasses) { $sAncestor = null; foreach($aClasses as $sClass) { if (is_null($sAncestor)) { // first loop $sAncestor = $sClass; } elseif ($sClass == $sAncestor) { // remains the same } elseif (self::GetRootClass($sClass) != self::GetRootClass($sAncestor)) { $sAncestor = null; break; } else { $sAncestor = self::LowestCommonAncestor($sAncestor, $sClass); } } return $sAncestor; } /** * Note: assumes that class A and B have a common ancestor */ protected static function LowestCommonAncestor($sClassA, $sClassB) { if ($sClassA == $sClassB) { $sRet = $sClassA; } elseif (is_subclass_of($sClassA, $sClassB)) { $sRet = $sClassB; } elseif (is_subclass_of($sClassB, $sClassA)) { $sRet = $sClassA; } else { // Recurse $sRet = self::LowestCommonAncestor($sClassA, self::GetParentClass($sClassB)); } return $sRet; } /** * Tells if a class contains a hierarchical key, and if so what is its AttCode * @return mixed String = sAttCode or false if the class is not part of a hierarchy */ public static function IsHierarchicalClass($sClass) { $sHierarchicalKeyCode = false; foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) { if ($oAtt->IsHierarchicalKey()) { $sHierarchicalKeyCode = $sAttCode; // Found the hierarchical key, no need to continue break; } } return $sHierarchicalKeyCode; } public static function EnumRootClasses() { return array_unique(self::$m_aRootClasses); } public static function EnumParentClasses($sClass, $iOption = ENUM_PARENT_CLASSES_EXCLUDELEAF, $bRootFirst = true) { self::_check_subclass($sClass); if ($bRootFirst) { $aRes = self::$m_aParentClasses[$sClass]; } else { $aRes = array_reverse(self::$m_aParentClasses[$sClass], true); } if ($iOption != ENUM_PARENT_CLASSES_EXCLUDELEAF) { if ($bRootFirst) { // Leaf class at the end $aRes[] = $sClass; } else { // Leaf class on top array_unshift($aRes, $sClass); } } return $aRes; } public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP) { self::_check_subclass($sClass); $aRes = self::$m_aChildClasses[$sClass]; if ($iOption != ENUM_CHILD_CLASSES_EXCLUDETOP) { // Add it to the list $aRes[] = $sClass; } return $aRes; } public static function EnumArchivableClasses() { $aRes = array(); foreach (self::GetClasses() as $sClass) { if (self::IsArchivable($sClass)) { $aRes[] = $sClass; } } return $aRes; } public static function HasChildrenClasses($sClass) { return (count(self::$m_aChildClasses[$sClass]) > 0); } public static function EnumCategories() { return array_keys(self::$m_Category2Class); } // Note: use EnumChildClasses to take the compound objects into account public static function GetSubclasses($sClass) { self::_check_subclass($sClass); $aSubClasses = array(); foreach(self::$m_aClassParams as $sSubClass => $foo) { if (is_subclass_of($sSubClass, $sClass)) { $aSubClasses[] = $sSubClass; } } return $aSubClasses; } public static function GetClasses($sCategories = '', $bStrict = false) { $aCategories = explode(',', $sCategories); $aClasses = array(); foreach($aCategories as $sCategory) { $sCategory = trim($sCategory); if (strlen($sCategory) == 0) { return array_keys(self::$m_aClassParams); } if (array_key_exists($sCategory, self::$m_Category2Class)) { $aClasses = array_merge($aClasses, self::$m_Category2Class[$sCategory]); } elseif ($bStrict) { throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}"); } } return array_unique($aClasses); } public static function HasTable($sClass) { if (strlen(self::DBGetTable($sClass)) == 0) return false; return true; } public static function IsAbstract($sClass) { $oReflection = new ReflectionClass($sClass); return $oReflection->isAbstract(); } /** * Normalizes query arguments and adds magic parameters: * - current_contact_id * - current_contact (DBObject) * - current_user (DBObject) * * @param array $aArgs Context arguments (some can be persistent objects) * @param array $aMoreArgs Other query parameters * @return array */ public static function PrepareQueryArguments($aArgs, $aMoreArgs = array()) { $aScalarArgs = array(); foreach(array_merge($aArgs, $aMoreArgs) as $sArgName => $value) { if (self::IsValidObject($value)) { if (strpos($sArgName, '->object()') === false) { // Normalize object arguments $aScalarArgs[$sArgName.'->object()'] = $value; } else { // Leave as is $aScalarArgs[$sArgName] = $value; } } else { if (is_scalar($value)) { $aScalarArgs[$sArgName] = (string) $value; } elseif (is_null($value)) { $aScalarArgs[$sArgName] = null; } elseif (is_array($value)) { $aScalarArgs[$sArgName] = $value; } } } // Add standard magic arguments // $aScalarArgs['current_contact_id'] = UserRights::GetContactId(); // legacy $oUser = UserRights::GetUserObject(); if (!is_null($oUser)) { $aScalarArgs['current_user->object()'] = $oUser; $oContact = UserRights::GetContactObject(); if (!is_null($oContact)) { $aScalarArgs['current_contact->object()'] = $oContact; } } return $aScalarArgs; } public static function MakeModifierProperties($oFilter) { // Compute query modifiers properties (can be set in the search itself, by the context, etc.) // $aModifierProperties = array(); foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier) { // Lowest precedence: the application context $aPluginProps = ApplicationContext::GetPluginProperties($sPluginClass); // Highest precedence: programmatically specified (or OQL) foreach($oFilter->GetModifierProperties($sPluginClass) as $sProp => $value) { $aPluginProps[$sProp] = $value; } if (count($aPluginProps) > 0) { $aModifierProperties[$sPluginClass] = $aPluginProps; } } return $aModifierProperties; } /** * Special processing for the hierarchical keys stored as nested sets * @param $iId integer The identifier of the parent * @param $oAttDef AttributeDefinition The attribute corresponding to the hierarchical key * @param $stable string The name of the database table containing the hierarchical key */ public static function HKInsertChildUnder($iId, $oAttDef, $sTable) { // Get the parent id.right value if ($iId == 0) { // No parent, insert completely at the right of the tree $sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`"; $aRes = CMDBSource::QueryToArray($sSQL); if (count($aRes) == 0) { $iMyRight = 1; } else { $iMyRight = $aRes[0]['max']+1; } } else { $sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".$iId; $iMyRight = CMDBSource::QueryToScalar($sSQL); $sSQLUpdateRight = "UPDATE `$sTable` SET `".$oAttDef->GetSQLRight()."` = `".$oAttDef->GetSQLRight()."` + 2 WHERE `".$oAttDef->GetSQLRight()."` >= $iMyRight"; CMDBSource::Query($sSQLUpdateRight); $sSQLUpdateLeft = "UPDATE `$sTable` SET `".$oAttDef->GetSQLLeft()."` = `".$oAttDef->GetSQLLeft()."` + 2 WHERE `".$oAttDef->GetSQLLeft()."` > $iMyRight"; CMDBSource::Query($sSQLUpdateLeft); } return array($oAttDef->GetSQLRight() => $iMyRight+1, $oAttDef->GetSQLLeft() => $iMyRight); } /** * Special processing for the hierarchical keys stored as nested sets: temporary remove the branch * @param $iId integer The identifier of the parent * @param $oAttDef AttributeDefinition The attribute corresponding to the hierarchical key * @param $sTable string The name of the database table containing the hierarchical key */ public static function HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable) { $iDelta = $iMyRight - $iMyLeft + 1; $sSQL = "UPDATE `$sTable` SET `".$oAttDef->GetSQLRight()."` = $iMyLeft - `".$oAttDef->GetSQLRight()."`, `".$oAttDef->GetSQLLeft()."` = $iMyLeft - `".$oAttDef->GetSQLLeft(); $sSQL .= "` WHERE `".$oAttDef->GetSQLLeft()."`> $iMyLeft AND `".$oAttDef->GetSQLRight()."`< $iMyRight"; CMDBSource::Query($sSQL); $sSQL = "UPDATE `$sTable` SET `".$oAttDef->GetSQLLeft()."` = `".$oAttDef->GetSQLLeft()."` - $iDelta WHERE `".$oAttDef->GetSQLLeft()."` > $iMyRight"; CMDBSource::Query($sSQL); $sSQL = "UPDATE `$sTable` SET `".$oAttDef->GetSQLRight()."` = `".$oAttDef->GetSQLRight()."` - $iDelta WHERE `".$oAttDef->GetSQLRight()."` > $iMyRight"; CMDBSource::Query($sSQL); } /** * Special processing for the hierarchical keys stored as nested sets: replug the temporary removed branch * @param $iId integer The identifier of the parent * @param $oAttDef AttributeDefinition The attribute corresponding to the hierarchical key * @param $sTable string The name of the database table containing the hierarchical key */ public static function HKReplugBranch($iNewLeft, $iNewRight, $oAttDef, $sTable) { $iDelta = $iNewRight - $iNewLeft + 1; $sSQL = "UPDATE `$sTable` SET `".$oAttDef->GetSQLLeft()."` = `".$oAttDef->GetSQLLeft()."` + $iDelta WHERE `".$oAttDef->GetSQLLeft()."` > $iNewLeft"; CMDBSource::Query($sSQL); $sSQL = "UPDATE `$sTable` SET `".$oAttDef->GetSQLRight()."` = `".$oAttDef->GetSQLRight()."` + $iDelta WHERE `".$oAttDef->GetSQLRight()."` >= $iNewLeft"; CMDBSource::Query($sSQL); $sSQL = "UPDATE `$sTable` SET `".$oAttDef->GetSQLRight()."` = $iNewLeft - `".$oAttDef->GetSQLRight()."`, `".$oAttDef->GetSQLLeft()."` = $iNewLeft - `".$oAttDef->GetSQLLeft()."` WHERE `".$oAttDef->GetSQLRight()."`< 0"; CMDBSource::Query($sSQL); } /** * Check (and updates if needed) the hierarchical keys * @param $bDiagnosticsOnly boolean If true only a diagnostic pass will be run, returning true or false * @param $bVerbose boolean Displays some information about what is done/what needs to be done * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB */ public static function CheckHKeys($bDiagnosticsOnly = false, $bVerbose = false, $bForceComputation = false) { $bChangeNeeded = 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->IsHierarchicalKey()) { if ($bVerbose) { echo "The attribute $sAttCode from $sClass is a hierarchical key.\n"; } $bResult = self::HKInit($sClass, $sAttCode, $bDiagnosticsOnly, $bVerbose, $bForceComputation); $bChangeNeeded |= $bResult; if ($bVerbose && !$bResult) { echo "Ok, the attribute $sAttCode from class $sClass seems up to date.\n"; } } } } return $bChangeNeeded; } /** * Initializes (i.e converts) a hierarchy stored using a 'parent_id' external key * into a hierarchy stored with a HierarchicalKey, by initializing the _left and _right values * to correspond to the existing hierarchy in the database * @param $sClass string Name of the class to process * @param $sAttCode string Code of the attribute to process * @param $bDiagnosticsOnly boolean If true only a diagnostic pass will be run, returning true or false * @param $bVerbose boolean Displays some information about what is done/what needs to be done * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB * @return true if an update is needed (diagnostics only) / was performed */ public static function HKInit($sClass, $sAttCode, $bDiagnosticsOnly = false, $bVerbose = false, $bForceComputation = false) { $idx = 1; $bUpdateNeeded = $bForceComputation; $oAttDef = self::GetAttributeDef($sClass, $sAttCode); $sTable = self::DBGetTable($sClass, $sAttCode); if ($oAttDef->IsHierarchicalKey()) { // Check if some values already exist in the table for the _right value, if so, do nothing $sRight = $oAttDef->GetSQLRight(); $sSQL = "SELECT MAX(`$sRight`) AS MaxRight FROM `$sTable`"; $iMaxRight = CMDBSource::QueryToScalar($sSQL); $sSQL = "SELECT COUNT(*) AS Count FROM `$sTable`"; // Note: COUNT(field) returns zero if the given field contains only NULLs $iCount = CMDBSource::QueryToScalar($sSQL); if (!$bForceComputation && ($iCount != 0) && ($iMaxRight == 0)) { $bUpdateNeeded = true; if ($bVerbose) { echo "The table '$sTable' must be updated to compute the fields $sRight and ".$oAttDef->GetSQLLeft()."\n"; } } if ($bForceComputation && !$bDiagnosticsOnly) { echo "Rebuilding the fields $sRight and ".$oAttDef->GetSQLLeft()." from table '$sTable'...\n"; } if ($bUpdateNeeded && !$bDiagnosticsOnly) { try { CMDBSource::Query('START TRANSACTION'); self::HKInitChildren($sTable, $sAttCode, $oAttDef, 0, $idx); CMDBSource::Query('COMMIT'); if ($bVerbose) { echo "Ok, table '$sTable' successfully updated.\n"; } } catch(Exception $e) { CMDBSource::Query('ROLLBACK'); throw new Exception("An error occured (".$e->getMessage().") while initializing the hierarchy for ($sClass, $sAttCode). The database was not modified."); } } } return $bUpdateNeeded; } /** * Recursive helper function called by HKInit */ protected static function HKInitChildren($sTable, $sAttCode, $oAttDef, $iId, &$iCurrIndex) { $sSQL = "SELECT id FROM `$sTable` WHERE `$sAttCode` = $iId"; $aRes = CMDBSource::QueryToArray($sSQL); $aTree = array(); $sLeft = $oAttDef->GetSQLLeft(); $sRight = $oAttDef->GetSQLRight(); foreach($aRes as $aValues) { $iChildId = $aValues['id']; $iLeft = $iCurrIndex++; $aChildren = self::HKInitChildren($sTable, $sAttCode, $oAttDef, $iChildId, $iCurrIndex); $iRight = $iCurrIndex++; $sSQL = "UPDATE `$sTable` SET `$sLeft` = $iLeft, `$sRight` = $iRight WHERE id= $iChildId"; 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'; $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL)); $bFixNeeded = false; if ($bVerbose && $oSet->Count() == 0) { echo "There are no Data Sources in the database.\n"; } while($oSource = $oSet->Fetch()) { if ($bVerbose) { echo "Checking Data Source '".$oSource->GetName()."'...\n"; $bFixNeeded = $bFixNeeded | $oSource->CheckDBConsistency($bDiagnostics, $bVerbose); } } if (!$bFixNeeded && $bVerbose) { echo "Ok.\n"; } return $bFixNeeded; } public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName) { if (!array_key_exists($sNewName, $aAliases)) { $aAliases[$sNewName] = $sRealName; return $sNewName; } for ($i = 1 ; $i < 100 ; $i++) { $sAnAlias = $sNewName.$i; if (!array_key_exists($sAnAlias, $aAliases)) { // Create that new alias $aAliases[$sAnAlias] = $sRealName; return $sAnAlias; } } throw new CoreException('Failed to create an alias', array('aliases' => $aAliases, 'new'=>$sNewName)); } public static function CheckDefinitions($bExitOnError = true) { if (count(self::GetClasses()) == 0) { throw new CoreException("MetaModel::InitClasses() has not been called, or no class has been declared ?!?!"); } $aErrors = array(); $aSugFix = array(); foreach (self::GetClasses() as $sClass) { $sTable = self::DBGetTable($sClass); $sTableLowercase = strtolower($sTable); if ($sTableLowercase != $sTable) { $aErrors[$sClass][] = "Table name '".$sTable."' has upper case characters. You might encounter issues when moving your installation between Linux and Windows."; $aSugFix[$sClass][] = "Use '$sTableLowercase' instead. Step 1: If already installed, then rename manually in the DB: RENAME TABLE `$sTable` TO `{$sTableLowercase}_tempname`, `{$sTableLowercase}_tempname` TO `$sTableLowercase`; Step 2: Rename the table in the datamodel and compile the application. Note: the MySQL statement provided in step 1 has been designed to be compatible with Windows or Linux."; } $aNameSpec = self::GetNameSpec($sClass); foreach($aNameSpec[1] as $i => $sAttCode) { if(!self::IsValidAttCode($sClass, $sAttCode)) { $aErrors[$sClass][] = "Unknown attribute code '".$sAttCode."' for the name definition"; $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); } } foreach(self::GetReconcKeys($sClass) as $sReconcKeyAttCode) { if (!empty($sReconcKeyAttCode) && !self::IsValidAttCode($sClass, $sReconcKeyAttCode)) { $aErrors[$sClass][] = "Unknown attribute code '".$sReconcKeyAttCode."' in the list of reconciliation keys"; $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); } } $bHasWritableAttribute = false; foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) { // It makes no sense to check the attributes again and again in the subclasses if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; if ($oAttDef->IsExternalKey()) { if (!self::IsValidClass($oAttDef->GetTargetClass())) { $aErrors[$sClass][] = "Unknown class '".$oAttDef->GetTargetClass()."' for the external key '$sAttCode'"; $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetClasses())."}"; } } elseif ($oAttDef->IsExternalField()) { $sKeyAttCode = $oAttDef->GetKeyAttCode(); if (!self::IsValidAttCode($sClass, $sKeyAttCode) || !self::IsValidKeyAttCode($sClass, $sKeyAttCode)) { $aErrors[$sClass][] = "Unknown key attribute code '".$sKeyAttCode."' for the external field $sAttCode"; $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sClass))."}"; } else { $oKeyAttDef = self::GetAttributeDef($sClass, $sKeyAttCode); $sTargetClass = $oKeyAttDef->GetTargetClass(); $sExtAttCode = $oAttDef->GetExtAttCode(); if (!self::IsValidAttCode($sTargetClass, $sExtAttCode)) { $aErrors[$sClass][] = "Unknown key attribute code '".$sExtAttCode."' for the external field $sAttCode"; $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sTargetClass))."}"; } } } else if ($oAttDef->IsLinkSet()) { // Do nothing... } else if ($oAttDef instanceof AttributeStopWatch) { $aThresholds = $oAttDef->ListThresholds(); if (is_array($aThresholds)) { foreach($aThresholds as $iPercent => $aDef) { if (array_key_exists('highlight', $aDef)) { if(!array_key_exists('code', $aDef['highlight'])) { $aErrors[$sClass][] = "The 'code' element is missing for the 'highlight' property of the $iPercent% threshold in the attribute: '$sAttCode'."; $aSugFix[$sClass][] = "Add a 'code' entry specifying the value of the highlight code for this threshold."; } else { $aScale = self::GetHighlightScale($sClass); if (!array_key_exists($aDef['highlight']['code'], $aScale)) { $aErrors[$sClass][] = "'{$aDef['highlight']['code']}' is not a valid value for the 'code' element of the $iPercent% threshold in the attribute: '$sAttCode'."; $aSugFix[$sClass][] = "The possible highlight codes for this class are: ".implode(', ', array_keys($aScale))."."; } } } } } } else // standard attributes { // Check that the default values definition is a valid object! $oValSetDef = $oAttDef->GetValuesDef(); if (!is_null($oValSetDef) && !$oValSetDef instanceof ValueSetDefinition) { $aErrors[$sClass][] = "Allowed values for attribute $sAttCode is not of the relevant type"; $aSugFix[$sClass][] = "Please set it as an instance of a ValueSetDefinition object."; } else { // Default value must be listed in the allowed values (if defined) $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); if (!is_null($aAllowedValues)) { $sDefaultValue = $oAttDef->GetDefaultValue(); if (is_string($sDefaultValue) && !array_key_exists($sDefaultValue, $aAllowedValues)) { $aErrors[$sClass][] = "Default value '".$sDefaultValue."' for attribute $sAttCode is not an allowed value"; $aSugFix[$sClass][] = "Please pickup the default value out of {'".implode(", ", array_keys($aAllowedValues))."'}"; } } } } // Check dependencies if ($oAttDef->IsWritable()) { $bHasWritableAttribute = true; foreach ($oAttDef->GetPrerequisiteAttributes() as $sDependOnAttCode) { if (!self::IsValidAttCode($sClass, $sDependOnAttCode)) { $aErrors[$sClass][] = "Unknown attribute code '".$sDependOnAttCode."' in the list of prerequisite attributes"; $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); } } } } foreach(self::GetClassFilterDefs($sClass) as $sFltCode=>$oFilterDef) { if (method_exists($oFilterDef, '__GetRefAttribute')) { $oAttDef = $oFilterDef->__GetRefAttribute(); if (!self::IsValidAttCode($sClass, $oAttDef->GetCode())) { $aErrors[$sClass][] = "Wrong attribute code '".$oAttDef->GetCode()."' (wrong class) for the \"basic\" filter $sFltCode"; $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; } } } // Lifecycle // $sStateAttCode = self::GetStateAttributeCode($sClass); if (strlen($sStateAttCode) > 0) { // Lifecycle - check that the state attribute does exist as an attribute if (!self::IsValidAttCode($sClass, $sStateAttCode)) { $aErrors[$sClass][] = "Unknown attribute code '".$sStateAttCode."' for the state definition"; $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; } else { // Lifecycle - check that there is a value set constraint on the state attribute $aAllowedValuesRaw = self::GetAllowedValues_att($sClass, $sStateAttCode); $aStates = array_keys(self::EnumStates($sClass)); if (is_null($aAllowedValuesRaw)) { $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' will reflect the state of the object. It must be restricted to a set of values"; $aSugFix[$sClass][] = "Please define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')]"; } else { $aAllowedValues = array_keys($aAllowedValuesRaw); // Lifecycle - check the the state attribute allowed values are defined states foreach($aAllowedValues as $sValue) { if (!in_array($sValue, $aStates)) { $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has an allowed value ($sValue) which is not a known state"; $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; } } // Lifecycle - check that defined states are allowed values foreach($aStates as $sStateValue) { if (!in_array($sStateValue, $aAllowedValues)) { $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has a state ($sStateValue) which is not an allowed value"; $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; } } } // Lifecycle - check that the action handlers are defined foreach (self::EnumStates($sClass) as $sStateCode => $aStateDef) { foreach(self::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) { foreach ($aTransitionDef['actions'] as $actionHandler) { if (is_string($actionHandler)) { if (!method_exists($sClass, $actionHandler)) { $aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]"; } } else // if(is_array($actionHandler)) { $sActionHandler = $actionHandler['verb']; if (!method_exists($sClass, $sActionHandler)) { $aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(...){return true;}]"; } } } } if (array_key_exists('highlight', $aStateDef)) { if(!array_key_exists('code', $aStateDef['highlight'])) { $aErrors[$sClass][] = "The 'code' element is missing for the 'highlight' property of state: '$sStateCode'."; $aSugFix[$sClass][] = "Add a 'code' entry specifying the value of the highlight code for this state."; } else { $aScale = self::GetHighlightScale($sClass); if (!array_key_exists($aStateDef['highlight']['code'], $aScale)) { $aErrors[$sClass][] = "'{$aStateDef['highlight']['code']}' is not a valid value for the 'code' element in the 'highlight' property of state: '$sStateCode'."; $aSugFix[$sClass][] = "The possible highlight codes for this class are: ".implode(', ', array_keys($aScale))."."; } } } } } } if ($bHasWritableAttribute) { if (!self::HasTable($sClass)) { $aErrors[$sClass][] = "No table has been defined for this class"; $aSugFix[$sClass][] = "Either define a table name or move the attributes elsewhere"; } } // ZList // foreach(self::EnumZLists() as $sListCode) { foreach (self::FlattenZList(self::GetZListItems($sClass, $sListCode)) as $sMyAttCode) { if (!self::IsValidAttCode($sClass, $sMyAttCode)) { $aErrors[$sClass][] = "Unknown attribute code '".$sMyAttCode."' from ZList '$sListCode'"; $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; } } } // Check unicity of the SQL columns // if (self::HasTable($sClass)) { $aTableColumns = array(); // array of column => attcode (the column is used by this attribute) $aTableColumns[self::DBGetKey($sClass)] = 'id'; // Check that SQL columns are declared only once // foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) { // Skip this attribute if not originaly defined in this class if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType) { if (array_key_exists($sField, $aTableColumns)) { $aErrors[$sClass][] = "Column '$sField' declared for attribute $sAttCode, but already used for attribute ".$aTableColumns[$sField]; $aSugFix[$sClass][] = "Please find another name for the SQL column"; } else { $aTableColumns[$sField] = $sAttCode; } } } } } // foreach class if (count($aErrors) > 0) { echo "Wrong declaration for class $sClass
\n"; echo "Aborting...
\n"; echo "