diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 94dc34d1a..ce22ff29a 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -6975,6 +6975,23 @@ class AttributeExternalKey extends AttributeDBFieldVoid return $oRet; } + /** + * Return the deletion option for the target attribute + * i.e. should the target object be deleted when the host object is deleted + * + * @return int DEL_AUTO (yes explicit), DEL_SILENT (yes implicit), DEL_MANUAL (no = default) + * + * @since 3.1.0 + */ + public function GetDeletionOptionForTargetObject() + { + if ($this->IsParam('on_host_delete')) { + return $this->Get('on_host_delete'); + } + + return $this->GetOptional('on_host_delete', DEL_MANUAL); + } + public static function GetFormFieldClass() { return '\\Combodo\\iTop\\Form\\Field\\SelectObjectField'; diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 514cf7a66..2e7d72f8d 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -4876,6 +4876,18 @@ abstract class DBObject implements iDisplay } $aVisited[$sClass] = $iThisId; + // Delete possible target objects + foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { + if ($oAttDef instanceof AttributeExternalKey && $oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) { + $iOption = $oAttDef->GetDeletionOptionForTargetObject(); + if ($iOption == DEL_SILENT || $iOption == DEL_AUTO) { + // Delete target object + $oTargetObject = MetaModel::GetObject($oAttDef->GetTargetClass(), $this->Get($sAttCode)); + $oTargetObject->MakeDeletionPlan($oDeletionPlan, $aVisited, $iOption); + } + } + } + if ($iDeleteOption == DEL_MANUAL) { // Stop the recursion here @@ -4889,13 +4901,13 @@ abstract class DBObject implements iDisplay $aDependentObjects = $this->GetReferencingObjects(true /* allow all data */); - // Getting and setting time limit are not symetric: + // Getting and setting time limit are not symmetric: // www.php.net/manual/fr/function.set-time-limit.php#72305 $iPreviousTimeLimit = ini_get('max_execution_time'); - foreach ($aDependentObjects as $sRemoteClass => $aPotentialDeletes) + foreach ($aDependentObjects as $aPotentialDeletes) { - foreach ($aPotentialDeletes as $sRemoteExtKey => $aData) + foreach ($aPotentialDeletes as $aData) { set_time_limit(intval($iLoopTimeLimit)); diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 0b72ec5ed..887c0b849 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -35,9 +35,9 @@ class DOMFormatException extends Exception * @param string $message * @param $code * @param $previous - * @param DOMNode $node [Optionnal] DOMNode causing the DOMFormatException + * @param DesignElement|null $node DOMNode causing the DOMFormatException */ - public function __construct($message, $code = null, $previous = null, DOMNode $node = null) + public function __construct($message, $code = null, $previous = null, DesignElement $node = null) { if($node !== null) { @@ -112,6 +112,11 @@ class MFCompiler protected $sEnvironment; protected $sCompilationTimeStamp; + /** @var array dynamic attributes definition + * @since 3.1.0 + */ + protected array $aDynamicAttributeDefinitions; + public function __construct($oModelFactory, $sEnvironment) { $this->oFactory = $oModelFactory; @@ -121,6 +126,7 @@ class MFCompiler $this->aCustomListsCodes = []; $this->aLog = []; + $this->aDynamicAttributeDefinitions = []; $this->sMainPHPCode = '<'.'?'."php\n"; $this->sMainPHPCode .= "/**\n"; $this->sMainPHPCode .= " * This file was automatically generated by the compiler on ".date('Y-m-d H:i:s')." -- DO NOT EDIT\n"; @@ -379,6 +385,7 @@ class MFCompiler $this->LoadSnippets(); $this->LoadGlobalEventListeners(); + $this->LoadDynamicAttributeDefinitions(); // Compile, module by module // @@ -542,6 +549,7 @@ EOF; } try { + /** @var \iTopWebPage $oP */ $aMenuLines = $this->CompileMenu($oMenuNode, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP); } catch (DOMFormatException $e) @@ -689,6 +697,7 @@ PHP; // Compile the branding // + /** @var \MFElement $oBrandingNode */ $oBrandingNode = $this->oFactory->GetNodes('branding')->item(0); $this->CompileBranding($oBrandingNode, $sTempTargetDir, $sFinalTargetDir); @@ -705,6 +714,7 @@ PHP; } // Compile the portals + /** @var \MFElement $oPortalsNode */ $oPortalsNode = $this->oFactory->GetNodes('/itop_design/portals')->item(0); $this->CompilePortals($oPortalsNode, $sTempTargetDir, $sFinalTargetDir); @@ -713,6 +723,7 @@ PHP; $this->CompileModuleDesigns($oModuleDesignsNode, $sTempTargetDir, $sFinalTargetDir); // Compile the XML parameters + /** @var \MFElement $oParametersNode */ $oParametersNode = $this->oFactory->GetNodes('/itop_design/module_parameters')->item(0); $this->CompileParameters($oParametersNode, $sTempTargetDir, $sFinalTargetDir); @@ -1252,10 +1263,10 @@ EOF $sClass = $oClass->getAttribute('id'); $oProperties = $oClass->GetUniqueElement('properties'); $sPHP = ''; - /** @var Contains dynamic CSS class definitions $sCss */ + /* Contains dynamic CSS class definitions */ $sCss = ''; - // Class caracteristics + // Class characteristics // $aClassParams = array(); $aClassParams['category'] = $this->GetPropString($oProperties, 'category', ''); @@ -1501,440 +1512,8 @@ EOF; $sAttCode = $oField->getAttribute('id'); $sAttType = $oField->getAttribute('xsi:type'); - $aDependencies = array(); - $oDependencies = $oField->GetOptionalElement('dependencies'); - if (!is_null($oDependencies)) - { - $oDepNodes = $oDependencies->getElementsByTagName('attribute'); - foreach($oDepNodes as $oDepAttribute) - { - $aDependencies[] = "'".$oDepAttribute->getAttribute('id')."'"; - } - } - $sDependencies = 'array('.implode(', ', $aDependencies).')'; - - $aParameters = array(); - - if ($sAttType == 'AttributeLinkedSetIndirect') - { - $aParameters['linked_class'] = $this->GetMandatoryPropString($oField, 'linked_class'); - $aParameters['ext_key_to_me'] = $this->GetMandatoryPropString($oField, 'ext_key_to_me'); - $aParameters['ext_key_to_remote'] = $this->GetMandatoryPropString($oField, 'ext_key_to_remote'); - $aParameters['allowed_values'] = 'null'; - $aParameters['count_min'] = $this->GetPropNumber($oField, 'count_min', 0); - $aParameters['count_max'] = $this->GetPropNumber($oField, 'count_max', 0); - $aParameters['duplicates'] = $this->GetPropBoolean($oField, 'duplicates', false); - $aParameters['depends_on'] = $sDependencies; - $aParameters['display_style'] = $this->GetPropString($oField, 'display_style'); - if ($sOql = $oField->GetChildText('filter')) { - $sEscapedOql = self::QuoteForPHP($sOql); - $aParameters['allowed_values'] = "new ValueSetObjects($sEscapedOql)"; - } else { - $aParameters['allowed_values'] = 'null'; - } - } - elseif ($sAttType == 'AttributeLinkedSet') - { - $aParameters['linked_class'] = $this->GetMandatoryPropString($oField, 'linked_class'); - $aParameters['ext_key_to_me'] = $this->GetMandatoryPropString($oField, 'ext_key_to_me'); - $aParameters['count_min'] = $this->GetPropNumber($oField, 'count_min', 0); - $aParameters['count_max'] = $this->GetPropNumber($oField, 'count_max', 0); - $aParameters['display_style'] = $this->GetPropString($oField, 'display_style'); - $sEditMode = $oField->GetChildText('edit_mode'); - if (!is_null($sEditMode)) - { - $aParameters['edit_mode'] = $this->EditModeToPHP($sEditMode); - } - if ($sOql = $oField->GetChildText('filter')) - { - $sEscapedOql = self::QuoteForPHP($sOql); - $aParameters['allowed_values'] = "new ValueSetObjects($sEscapedOql)"; - } - else - { - $aParameters['allowed_values'] = 'null'; - } - $aParameters['depends_on'] = $sDependencies; - } - elseif ($sAttType == 'AttributeExternalKey') - { - $aParameters['targetclass'] = $this->GetPropString($oField, 'target_class', ''); - // deprecated: $aParameters['jointype'] = 'null'; - if ($sOql = $oField->GetChildText('filter')) - { - $sEscapedOql = self::QuoteForPHP($sOql); - $aParameters['allowed_values'] = "new ValueSetObjects($sEscapedOql)"; // or "new ValueSetObjects('SELECT xxxx')" - } - else - { - $aParameters['allowed_values'] = 'null'; // or "new ValueSetObjects('SELECT xxxx')" - } - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['on_target_delete'] = $oField->GetChildText('on_target_delete'); - $aParameters['depends_on'] = $sDependencies; - $aParameters['max_combo_length'] = $this->GetPropNumber($oField, 'max_combo_length'); - $aParameters['min_autocomplete_chars'] = $this->GetPropNumber($oField, 'min_autocomplete_chars'); - $aParameters['allow_target_creation'] = $this->GetPropBoolean($oField, 'allow_target_creation'); - $aParameters['display_style'] = $this->GetPropString($oField, 'display_style', 'select'); - } - elseif ($sAttType == 'AttributeObjectKey') - { - $aParameters['class_attcode'] = $this->GetMandatoryPropString($oField, 'class_attcode'); - $aParameters['allowed_values'] = 'null'; - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - } - elseif ($sAttType == 'AttributeHierarchicalKey') - { - if ($sOql = $oField->GetChildText('filter')) - { - $sEscapedOql = self::QuoteForPHP($sOql); - $aParameters['allowed_values'] = "new ValueSetObjects($sEscapedOql)"; // or "new ValueSetObjects('SELECT xxxx')" - } - else - { - $aParameters['allowed_values'] = 'null'; // or "new ValueSetObjects('SELECT xxxx')" - } - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['on_target_delete'] = $oField->GetChildText('on_target_delete'); - $aParameters['depends_on'] = $sDependencies; - $aParameters['max_combo_length'] = $this->GetPropNumber($oField, 'max_combo_length'); - $aParameters['min_autocomplete_chars'] = $this->GetPropNumber($oField, 'min_autocomplete_chars'); - $aParameters['allow_target_creation'] = $this->GetPropBoolean($oField, 'allow_target_creation'); - } - elseif ($sAttType == 'AttributeExternalField') - { - $aParameters['allowed_values'] = 'null'; - $aParameters['extkey_attcode'] = $this->GetMandatoryPropString($oField, 'extkey_attcode'); - $aParameters['target_attcode'] = $this->GetMandatoryPropString($oField, 'target_attcode'); - } - elseif ($sAttType == 'AttributeURL') - { - $aParameters['target'] = $this->GetPropString($oField, 'target', ''); - $aParameters['allowed_values'] = 'null'; - $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; - } - elseif ($sAttType == 'AttributeEnum') - { - $oValues = $oField->GetUniqueElement('values'); - $oValueNodes = $oValues->getElementsByTagName('value'); - $aValues = []; - $aStyledValues = []; - foreach($oValueNodes as $oValue) - { - // New in 3.0 the format of values changed - $sCode = $this->GetMandatoryPropString($oValue, 'code', false); - $aValues[] = $sCode; - $oStyleNode = $oValue->GetOptionalElement('style'); - if ($oStyleNode) { - $aEnumStyleData = $this->GenerateStyleDataFromNode($oStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode, $sCode); - $aStyledValues[] = $aEnumStyleData['orm_style_instantiation']; - $sCss .= $aEnumStyleData['scss']; - } - } - $sValues = '"'.implode(',', $aValues).'"'; - $aParameters['allowed_values'] = "new ValueSetEnum($sValues)"; - if (count($aStyledValues) > 0) { - $sStyledValues = "[".implode(',', $aStyledValues)."]"; - $aParameters['styled_values'] = "$sStyledValues"; - } - $oDefaultStyleNode = $oField->GetOptionalElement('default_style'); - if ($oDefaultStyleNode) { - $aEnumStyleData = $this->GenerateStyleDataFromNode($oDefaultStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode); - $aParameters['default_style'] = $aEnumStyleData['orm_style_instantiation']; - $sCss .= $aEnumStyleData['scss']; - } - $aParameters['display_style'] = $this->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; - } - elseif ($sAttType == 'AttributeMetaEnum') - { - $oValues = $oField->GetUniqueElement('values'); - $oValueNodes = $oValues->getElementsByTagName('value'); - $aValues = []; - $aStyledValues = []; - foreach($oValueNodes as $oValue) { - // New in 3.0 the format of values changed - $sCode = $this->GetMandatoryPropString($oValue, 'code', false); - $aValues[] = $sCode; - $oStyleNode = $oValue->GetOptionalElement('style'); - if ($oStyleNode) { - $aEnumStyleData = $this->GenerateStyleDataFromNode($oStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode, $sCode); - $aStyledValues[] = $aEnumStyleData['orm_style_instantiation']; - $sCss .= $aEnumStyleData['scss']; - } - } - // new style... $sValues = 'array('.implode(', ', $aValues).')'; - $sValues = '"'.implode(',', $aValues).'"'; - if (count($aStyledValues) > 0) { - $sStyledValues = "[".implode(',', $aStyledValues)."]"; - $aParameters['styled_values'] = "$sStyledValues"; - } - $oDefaultStyleNode = $oField->GetOptionalElement('default_style'); - if ($oDefaultStyleNode) { - $aEnumStyleData = $this->GenerateStyleDataFromNode($oDefaultStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode); - $aParameters['default_style'] = $aEnumStyleData['orm_style_instantiation']; - $sCss .= $aEnumStyleData['scss']; - } - $aParameters['allowed_values'] = "new ValueSetEnum($sValues)"; - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['default_value'] = $this->GetPropString($oField, 'default_value', ''); - - $oMappings = $oField->GetUniqueElement('mappings'); - $oMappingNodes = $oMappings->getElementsByTagName('mapping'); - $aMapping = array(); - foreach ($oMappingNodes as $oMapping) - { - $sMappingId = $oMapping->getAttribute('id'); - $sMappingAttCode = $oMapping->GetChildText('attcode'); - $aMapping[$sMappingId]['attcode'] = $sMappingAttCode; - $aMapping[$sMappingId]['values'] = array(); - $oMetaValues = $oMapping->GetUniqueElement('metavalues'); - foreach ($oMetaValues->getElementsByTagName('metavalue') as $oMetaValue) - { - $sMetaValue = $oMetaValue->getAttribute('id'); - $oValues = $oMetaValue->GetUniqueElement('values'); - foreach ($oValues->getElementsByTagName('value') as $oValue) - { - $sValue = $oValue->getAttribute('id'); - $aMapping[$sMappingId]['values'][$sValue] = $sMetaValue; - } - - } - } - $aParameters['mapping'] = var_export($aMapping, true); - } - elseif ($sAttType == 'AttributeBlob') - { - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - } - elseif ($sAttType == 'AttributeImage') - { - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - $aParameters['display_max_width'] = $this->GetPropNumber($oField, 'display_max_width', 128); - $aParameters['display_max_height'] = $this->GetPropNumber($oField, 'display_max_height', 128); - $aParameters['storage_max_width'] = $this->GetPropNumber($oField, 'storage_max_width', 256); - $aParameters['storage_max_height'] = $this->GetPropNumber($oField, 'storage_max_height', 256); - - if (($sDefault = $oField->GetChildText('default_image')) && (strlen($sDefault) > 0)) - { - $aParameters['default_image'] = "utils::GetAbsoluteUrlModulesRoot().'$sModuleRelativeDir/$sDefault'"; - } - else - { - $aParameters['default_image'] = 'null'; - } - } - elseif ($sAttType == 'AttributeStopWatch') - { - $oStates = $oField->GetUniqueElement('states'); - $oStateNodes = $oStates->getElementsByTagName('state'); - $aStates = array(); - foreach($oStateNodes as $oState) - { - $aStates[] = '"'.$oState->GetAttribute('id').'"'; - } - $aParameters['states'] = 'array('.implode(', ', $aStates).')'; - - $aParameters['goal_computing'] = $this->GetPropString($oField, 'goal', 'DefaultMetricComputer'); // Optional, no deadline by default - $aParameters['working_time_computing'] = $this->GetPropString($oField, 'working_time', ''); // Blank (different than DefaultWorkingTimeComputer) - - $oThresholds = $oField->GetUniqueElement('thresholds'); - $oThresholdNodes = $oThresholds->getElementsByTagName('threshold'); - $aThresholds = array(); - foreach($oThresholdNodes as $oThreshold) - { - $iPercent = (int)$oThreshold->getAttribute('id'); - - $oHighlight = $oThreshold->GetUniqueElement('highlight', false); - $sHighlight = ''; - if($oHighlight) - { - $sCode = $oHighlight->GetChildText('code'); - $sPersistent = $this->GetPropBoolean($oHighlight, 'persistent', false); - $sHighlight = "'highlight' => array('code' => '$sCode', 'persistent' => $sPersistent), "; - } - - $oActions = $oThreshold->GetUniqueElement('actions'); - $oActionNodes = $oActions->getElementsByTagName('action'); - $aActions = array(); - foreach($oActionNodes as $oAction) - { - $oParams = $oAction->GetOptionalElement('params'); - $aActionParams = array(); - if ($oParams) - { - $oParamNodes = $oParams->getElementsByTagName('param'); - foreach($oParamNodes as $oParam) - { - $sParamType = $oParam->getAttribute('xsi:type'); - if ($sParamType == '') - { - $sParamType = 'string'; - } - $aActionParams[] = "array('type' => '$sParamType', 'value' => ".self::QuoteForPHP($oParam->textContent).")"; - } - } - $sActionParams = 'array('.implode(', ', $aActionParams).')'; - $sVerb = $this->GetPropString($oAction, 'verb'); - $aActions[] = "array('verb' => $sVerb, 'params' => $sActionParams)"; - } - $sActions = 'array('.implode(', ', $aActions).')'; - $aThresholds[] = $iPercent." => array('percent' => $iPercent, $sHighlight 'actions' => $sActions)"; - } - $aParameters['thresholds'] = 'array('.implode(', ', $aThresholds).')'; - } - elseif ($sAttType == 'AttributeSubItem') - { - $aParameters['target_attcode'] = $this->GetMandatoryPropString($oField, 'target_attcode'); - $aParameters['item_code'] = $this->GetMandatoryPropString($oField, 'item_code'); - } - elseif ($sAttType == 'AttributeRedundancySettings') - { - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['relation_code'] = $this->GetMandatoryPropString($oField, 'relation_code'); - $aParameters['from_class'] = $this->GetMandatoryPropString($oField, 'from_class'); - $aParameters['neighbour_id'] = $this->GetMandatoryPropString($oField, 'neighbour_id'); - $aParameters['enabled'] = $this->GetMandatoryPropBoolean($oField, 'enabled'); - $aParameters['enabled_mode'] = $this->GetMandatoryPropString($oField, 'enabled_mode'); - $aParameters['min_up'] = $this->GetMandatoryPropNumber($oField, 'min_up'); - $aParameters['min_up_mode'] = $this->GetMandatoryPropString($oField, 'min_up_mode'); - $aParameters['min_up_type'] = $this->GetMandatoryPropString($oField, 'min_up_type'); - } - elseif ($sAttType == 'AttributeCustomFields') - { - $aParameters['handler_class'] = $this->GetMandatoryPropString($oField, 'handler_class'); - } - elseif ($sAttType == 'AttributeTagSet') - { - $aTagFieldsInfo[] = $sAttCode; - $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - $aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12); - $aParameters['tag_code_max_len'] = $this->GetPropNumber($oField, 'tag_code_max_len', 20); - if ($aParameters['tag_code_max_len'] > 255) - { - $aParameters['tag_code_max_len'] = 255; - } - } - elseif ($sAttType == 'AttributeClassAttCodeSet') - { - $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - $aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12); - $aParameters['class_field'] = $this->GetMandatoryPropString($oField, 'class_field'); - // List of AttributeDefinition Classes to filter class_field (empty means all) - $aParameters['attribute_definition_list'] = $this->GetPropString($oField, 'attribute_definition_list', ''); - // Exclusion list of AttributeDefinition Classes to filter class_field (empty means no exclusion) - $aParameters['attribute_definition_exclusion_list'] = $this->GetPropString($oField, 'attribute_definition_exclusion_list', ''); - } - elseif ($sAttType == 'AttributeEnumSet') - { - $oValues = $oField->GetUniqueElement('values'); - $oValueNodes = $oValues->getElementsByTagName('value'); - $aValues = []; - foreach($oValueNodes as $oValue) - { - // New in 3.0 the format of values changed - $sCode = $this->GetMandatoryPropString($oValue, 'code', false); - $aValues[] = $sCode; - } - $sValues = '"'.implode(',', $aValues).'"'; - $aParameters['allowed_values'] = 'null'; - $aParameters['possible_values'] = "new ValueSetEnumPadded($sValues)"; - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - $aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12); - } - elseif ($sAttType == 'AttributeQueryAttCodeSet') - { - $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - $aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12); - $aParameters['query_field'] = $this->GetMandatoryPropString($oField, 'query_field'); - } - elseif ($sAttType == 'AttributeClassState') - { - $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - $aParameters['class_field'] = $this->GetMandatoryPropString($oField, 'class_field'); - } - elseif ($sAttType == 'AttributeDashboard') - { - $aParameters['is_user_editable'] = $this->GetPropBoolean($oField, 'is_user_editable', true); - $aParameters['definition_file'] = $this->GetPropString($oField, 'definition_file'); - - if ($aParameters['definition_file'] == null) - { - $oDashboardDefinition = $oField->GetOptionalElement('definition'); - if ($oDashboardDefinition == null) - { - throw(new DOMFormatException('Missing definition for Dashboard Attribute "'.$sAttCode.'" expecting either a tag "definition_file" or "definition".')); - } - $sFileName = strtolower($sClass).'__'.strtolower($sAttCode).'_dashboard.xml'; - - $oXMLDoc = new DOMDocument('1.0', 'UTF-8'); - $oXMLDoc->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS) - $oXMLDoc->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect - - $oRootNode = $oXMLDoc->createElement('dashboard'); // make sure that the document is not empty - $oRootNode->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance"); - $oXMLDoc->appendChild($oRootNode); - foreach ($oDashboardDefinition->childNodes as $oNode) - { - $oDefNode = $oXMLDoc->importNode($oNode, true); // layout, cells, etc Nodes and below - $oRootNode->appendChild($oDefNode); - } - $sFileName = $sModuleRelativeDir.'/'.$sFileName; - $oXMLDoc->save($sTempTargetDir.'/'.$sFileName); - $aParameters['definition_file'] = "'".str_replace("'", "\\'", $sFileName)."'"; - } - } - else - { - $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" - $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; - } - - // Optional parameters (more for historical reasons) - // Added if present... - // - $aParameters['validation_pattern'] = $this->GetPropString($oField, 'validation_pattern'); - $aParameters['format'] = $this->GetPropString($oField, 'format'); - $aParameters['width'] = $this->GetPropString($oField, 'width'); - $aParameters['height'] = $this->GetPropString($oField, 'height'); - $aParameters['digits'] = $this->GetPropNumber($oField, 'digits'); - $aParameters['decimals'] = $this->GetPropNumber($oField, 'decimals'); - $aParameters['always_load_in_tables'] = $this->GetPropBoolean($oField, 'always_load_in_tables', false); - $sTrackingLevel = $oField->GetChildText('tracking_level'); - if (!is_null($sTrackingLevel)) - { - $aParameters['tracking_level'] = $this->TrackingLevelToPHP($sAttType, $sTrackingLevel); - } + $aParameters = $this->CompileAttribute($sAttType, $oField, $sModuleRelativeDir, $sClass, $sAttCode, $sCss, $aTagFieldsInfo, $sTempTargetDir); $aParams = array(); foreach($aParameters as $sKey => $sValue) @@ -2293,19 +1872,11 @@ EOF; throw new DOMFormatException("Relation '$sRelationId/$sNeighbourId': both a query and and attribute have been specified... which one should be used?"); } - if ($sDirection == 'both') - { - if (($sAttribute == '') && ($sQueryUp == '')) - { + if ($sDirection == 'both') { + if (($sAttribute == '') && ($sQueryUp == '')) { throw new DOMFormatException("Relation '$sRelationId/$sNeighbourId': missing the query_up specification"); } - } - elseif ($sDirection == 'down') - { - // Ok - } - else - { + } elseif ($sDirection != 'down') { throw new DOMFormatException("Relation '$sRelationId/$sNeighbourId': unknown direction ($sDirection), expecting 'both' or 'down'"); } $aRelations[$sRelationId][$sNeighbourId] = array( @@ -2361,12 +1932,6 @@ EOF; { $aRequiredFiles[] = $sIncludeFile; } -//TODO fix this !!! -// $sFullPath = $this->sSourceDir.'/'.$sModuleRelativeDir.'/'.$sIncludeFile; -// if (!file_exists($sFullPath)) -// { -// throw new Exception("Failed to process class '".$oClass->getAttribute('id')."', from '$sModuleRelativeDir'. The required include file: '$sFullPath' does not exist."); -// } } else { @@ -2425,10 +1990,537 @@ EOF return $sPHP; } + /** + * @param string $sAttType + * @param \DOMElement $oField + * @param string $sModuleRelativeDir + * @param string $sClass + * @param string $sAttCode + * @param string $sCss + * @param array $aTagFieldsInfo + * @param string $sTempTargetDir + * + * @return array + * @throws \DOMException + * @throws \DOMFormatException + */ + public function CompileAttribute(string $sAttType, DOMElement $oField, string $sModuleRelativeDir, string $sClass, string $sAttCode, string &$sCss, array &$aTagFieldsInfo, string $sTempTargetDir): array + { + $aParameters = []; + + $aDependencies = array(); + $oDependencies = $oField->GetOptionalElement('dependencies'); + if (!is_null($oDependencies)) + { + $oDepNodes = $oDependencies->getElementsByTagName('attribute'); + foreach($oDepNodes as $oDepAttribute) + { + $aDependencies[] = "'".$oDepAttribute->getAttribute('id')."'"; + } + } + $sDependencies = 'array('.implode(', ', $aDependencies).')'; + + // Check dynamic attribute definition first + if ($this->HasDynamicAttributeDefinition($sAttType)) { + $this->CompileDynamicAttribute($sAttType, $oField, $aParameters, $sModuleRelativeDir, $sClass, $sCss, $sDependencies); + } elseif ($sAttType == 'AttributeLinkedSetIndirect') { + $this->CompileCommonProperty('linked_class', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('ext_key_to_me', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('ext_key_to_remote', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('count_min', $oField, $aParameters, $sModuleRelativeDir, 0); + $this->CompileCommonProperty('count_max', $oField, $aParameters, $sModuleRelativeDir, 0); + $this->CompileCommonProperty('duplicates', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeLinkedSet') { + $this->CompileCommonProperty('linked_class', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('ext_key_to_me', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('count_min', $oField, $aParameters, $sModuleRelativeDir, 0); + $this->CompileCommonProperty('count_max', $oField, $aParameters, $sModuleRelativeDir, 0); + $this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('edit_mode', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeExternalKey') { + $this->CompileCommonProperty('target_class', $oField, $aParameters, $sModuleRelativeDir, ''); + $this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('on_target_delete', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('max_combo_length', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('min_autocomplete_chars', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('allow_target_creation', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir, 'select'); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeObjectKey') { + $this->CompileCommonProperty('class_attcode', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeHierarchicalKey') { + $this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('on_target_delete', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('max_combo_length', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('min_autocomplete_chars', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('allow_target_creation', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeExternalField') { + $this->CompileCommonProperty('extkey_attcode', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('target_attcode', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + } elseif ($sAttType == 'AttributeURL') { + $this->CompileCommonProperty('target', $oField, $aParameters, $sModuleRelativeDir, ''); + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('default_value', $oField, $aParameters, $sModuleRelativeDir, ''); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeEnum') { + $oValues = $oField->GetUniqueElement('values'); + $oValueNodes = $oValues->getElementsByTagName('value'); + $aValues = []; + $aStyledValues = []; + foreach ($oValueNodes as $oValue) { + // New in 3.0 the format of values changed + $sCode = $this->GetMandatoryPropString($oValue, 'code', false); + $aValues[] = $sCode; + $oStyleNode = $oValue->GetOptionalElement('style'); + if ($oStyleNode) { + $aEnumStyleData = $this->GenerateStyleDataFromNode($oStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode, $sCode); + $aStyledValues[] = $aEnumStyleData['orm_style_instantiation']; + $sCss .= $aEnumStyleData['scss']; + } + } + $sValues = '"'.implode(',', $aValues).'"'; + $aParameters['allowed_values'] = "new ValueSetEnum($sValues)"; + if (count($aStyledValues) > 0) { + $sStyledValues = '['.implode(',', $aStyledValues).']'; + $aParameters['styled_values'] = "$sStyledValues"; + } + $aParameters['allowed_values'] = "new ValueSetEnum($sValues)"; + $oDefaultStyleNode = $oField->GetOptionalElement('default_style'); + if ($oDefaultStyleNode) { + $aEnumStyleData = $this->GenerateStyleDataFromNode($oDefaultStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode); + $aParameters['default_style'] = $aEnumStyleData['orm_style_instantiation']; + $sCss .= $aEnumStyleData['scss']; + } + $this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir, 'list'); + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('default_value', $oField, $aParameters, $sModuleRelativeDir, ''); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeMetaEnum') { + $oValues = $oField->GetUniqueElement('values'); + $oValueNodes = $oValues->getElementsByTagName('value'); + $aValues = []; + $aStyledValues = []; + foreach ($oValueNodes as $oValue) { + // New in 3.0 the format of values changed + $sCode = $this->GetMandatoryPropString($oValue, 'code', false); + $aValues[] = $sCode; + $oStyleNode = $oValue->GetOptionalElement('style'); + if ($oStyleNode) { + $aEnumStyleData = $this->GenerateStyleDataFromNode($oStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode, $sCode); + $aStyledValues[] = $aEnumStyleData['orm_style_instantiation']; + $sCss .= $aEnumStyleData['scss']; + } + } + $sValues = '"'.implode(',', $aValues).'"'; + $aParameters['allowed_values'] = "new ValueSetEnum($sValues)"; + if (count($aStyledValues) > 0) { + $sStyledValues = '['.implode(',', $aStyledValues).']'; + $aParameters['styled_values'] = "$sStyledValues"; + } + $aParameters['allowed_values'] = "new ValueSetEnum($sValues)"; + $oDefaultStyleNode = $oField->GetOptionalElement('default_style'); + if ($oDefaultStyleNode) { + $aEnumStyleData = $this->GenerateStyleDataFromNode($oDefaultStyleNode, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode); + $aParameters['default_style'] = $aEnumStyleData['orm_style_instantiation']; + $sCss .= $aEnumStyleData['scss']; + } + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('default_value', $oField, $aParameters, $sModuleRelativeDir, ''); + $this->CompileCommonProperty('mappings', $oField, $aParameters, $sModuleRelativeDir); + } elseif ($sAttType == 'AttributeBlob') { + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeImage') { + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('display_max_width', $oField, $aParameters, $sModuleRelativeDir, 128); + $this->CompileCommonProperty('display_max_height', $oField, $aParameters, $sModuleRelativeDir, 128); + $this->CompileCommonProperty('storage_max_width', $oField, $aParameters, $sModuleRelativeDir, 256); + $this->CompileCommonProperty('storage_max_height', $oField, $aParameters, $sModuleRelativeDir, 256); + $this->CompileCommonProperty('default_image', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeStopWatch') { + $this->CompileCommonProperty('states', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('goal', $oField, $aParameters, $sModuleRelativeDir, 'DefaultMetricComputer'); + $this->CompileCommonProperty('working_time', $oField, $aParameters, $sModuleRelativeDir, ''); + $this->CompileCommonProperty('thresholds', $oField, $aParameters, $sModuleRelativeDir); + } elseif ($sAttType == 'AttributeSubItem') { + $this->CompileCommonProperty('target_attcode', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('item_code', $oField, $aParameters, $sModuleRelativeDir); + } elseif ($sAttType == 'AttributeRedundancySettings') { + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('relation_code', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('from_class', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('neighbour_id', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('enabled', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('enabled_mode', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('min_up', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('min_up_mode', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('min_up_type', $oField, $aParameters, $sModuleRelativeDir); + } elseif ($sAttType == 'AttributeCustomFields') { + $this->CompileCommonProperty('handler_class', $oField, $aParameters, $sModuleRelativeDir); + } elseif ($sAttType == 'AttributeTagSet') { + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('max_items', $oField, $aParameters, $sModuleRelativeDir, 12); + $this->CompileCommonProperty('tag_code_max_len', $oField, $aParameters, $sModuleRelativeDir, 20); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + if ($aParameters['tag_code_max_len'] > 255) { + $aParameters['tag_code_max_len'] = 255; + } + $aTagFieldsInfo[] = $sAttCode; + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeClassAttCodeSet') { + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('max_items', $oField, $aParameters, $sModuleRelativeDir, 12); + $this->CompileCommonProperty('class_field', $oField, $aParameters, $sModuleRelativeDir); + // List of AttributeDefinition Classes to filter class_field (empty means all) + $this->CompileCommonProperty('attribute_definition_list', $oField, $aParameters, $sModuleRelativeDir, ''); + // Exclusion list of AttributeDefinition Classes to filter class_field (empty means no exclusion) + $this->CompileCommonProperty('attribute_definition_exclusion_list', $oField, $aParameters, $sModuleRelativeDir, ''); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeEnumSet') { + $oValues = $oField->GetUniqueElement('values'); + $oValueNodes = $oValues->getElementsByTagName('value'); + $aValues = []; + foreach ($oValueNodes as $oValue) { + // New in 3.0 the format of values changed + $sCode = $this->GetMandatoryPropString($oValue, 'code', false); + $aValues[] = $sCode; + } + $sValues = '"'.implode(',', $aValues).'"'; + $aParameters['possible_values'] = "new ValueSetEnumPadded($sValues)"; + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('max_items', $oField, $aParameters, $sModuleRelativeDir, 12); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeQueryAttCodeSet') { + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('max_items', $oField, $aParameters, $sModuleRelativeDir, 12); + $this->CompileCommonProperty('query_field', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeClassState') { + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('class_field', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } elseif ($sAttType == 'AttributeDashboard') { + $this->CompileCommonProperty('is_user_editable', $oField, $aParameters, $sModuleRelativeDir, true); + $aParameters['definition_file'] = $this->GetPropString($oField, 'definition_file'); + if ($aParameters['definition_file'] == null) { + $oDashboardDefinition = $oField->GetOptionalElement('definition'); + if ($oDashboardDefinition == null) { + throw(new DOMFormatException('Missing definition for Dashboard Attribute "'.$sAttCode.'" expecting either a tag "definition_file" or "definition".')); + } + $sFileName = strtolower($sClass).'__'.strtolower($sAttCode).'_dashboard.xml'; + + $oXMLDoc = new DOMDocument('1.0', 'UTF-8'); + $oXMLDoc->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS) + $oXMLDoc->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect + + $oRootNode = $oXMLDoc->createElement('dashboard'); // make sure that the document is not empty + $oRootNode->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $oXMLDoc->appendChild($oRootNode); + foreach ($oDashboardDefinition->childNodes as $oNode) { + $oDefNode = $oXMLDoc->importNode($oNode, true); // layout, cells, etc Nodes and below + $oRootNode->appendChild($oDefNode); + } + $sFileName = $sModuleRelativeDir.'/'.$sFileName; + $oXMLDoc->save($sTempTargetDir.'/'.$sFileName); + $aParameters['definition_file'] = "'".str_replace("'", "\\'", $sFileName)."'"; + } + } else { + $this->CompileCommonProperty('sql', $oField, $aParameters, $sModuleRelativeDir); + $this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false); + $this->CompileCommonProperty('default_value', $oField, $aParameters, $sModuleRelativeDir, ''); + $this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir); + $aParameters['depends_on'] = $sDependencies; + } + + // Optional parameters (more for historical reasons) + // Added if present... + // + $aParameters['validation_pattern'] = $this->GetPropString($oField, 'validation_pattern'); + $aParameters['format'] = $this->GetPropString($oField, 'format'); + $aParameters['width'] = $this->GetPropString($oField, 'width'); + $aParameters['height'] = $this->GetPropString($oField, 'height'); + $aParameters['digits'] = $this->GetPropNumber($oField, 'digits'); + $aParameters['decimals'] = $this->GetPropNumber($oField, 'decimals'); + $aParameters['always_load_in_tables'] = $this->GetPropBoolean($oField, 'always_load_in_tables', false); + $sTrackingLevel = $oField->GetChildText('tracking_level'); + if (!is_null($sTrackingLevel)) { + $aParameters['tracking_level'] = $this->TrackingLevelToPHP($sAttType, $sTrackingLevel); + } + + return $aParameters; + } + + /** + * @param string $sPropertyName + * @param \DOMElement $oField + * @param array $aParameters + * @param mixed $default + * + * @return bool true if the property was found and compiled + * @throws \DOMFormatException + */ + protected function CompileCommonProperty(string $sPropertyName, DOMElement $oField, array &$aParameters, string $sModuleRelativeDir, $default = null): bool + { + switch ($sPropertyName) { + case 'linked_class': + case 'ext_key_to_me': + case 'ext_key_to_remote': + case 'sql': + case 'class_attcode': + case 'extkey_attcode': + case 'target_attcode': + case 'item_code': + case 'relation_code': + case 'from_class': + case 'neighbour_id': + case 'enabled_mode': + case 'min_up_mode': + case 'min_up_type': + case 'handler_class': + case 'class_field': + case 'query_field': + $aParameters[$sPropertyName] = $this->GetMandatoryPropString($oField, $sPropertyName); + break; + case 'display_style': + case 'target': + case 'default_value': + case 'attribute_definition_list': + case 'attribute_definition_exclusion_list': + $aParameters[$sPropertyName] = $this->GetPropString($oField, $sPropertyName, $default); + break; + case 'min_up': + $aParameters[$sPropertyName] = $this->GetMandatoryPropNumber($oField, $sPropertyName); + break; + case 'count_min': + case 'count_max': + case 'max_combo_length': + case 'min_autocomplete_chars': + case 'display_max_width': + case 'display_max_height': + case 'storage_max_width': + case 'storage_max_height': + case 'max_items': + case 'tag_code_max_len': + $aParameters[$sPropertyName] = $this->GetPropNumber($oField, $sPropertyName, $default); + break; + case 'enabled': + $aParameters[$sPropertyName] = $this->GetMandatoryPropBoolean($oField, $sPropertyName); + break; + case 'duplicates': + case 'is_null_allowed': + case 'allow_target_creation': + case 'is_user_editable': + $aParameters[$sPropertyName] = $this->GetPropBoolean($oField, $sPropertyName, $default); + break; + case 'on_target_delete': + $aParameters[$sPropertyName] = $oField->GetChildText($sPropertyName); + break; + case 'allowed_values': + $aParameters[$sPropertyName] = 'null'; + break; + case 'filter': + if ($sOql = $oField->GetChildText('filter')) { + $sEscapedOql = self::QuoteForPHP($sOql); + $aParameters['allowed_values'] = "new ValueSetObjects($sEscapedOql)"; // or "new ValueSetObjects('SELECT xxxx')" + } else { + $aParameters['allowed_values'] = 'null'; + } + break; + case 'edit_mode': + $sEditMode = $oField->GetChildText('edit_mode'); + if (!is_null($sEditMode)) { + $aParameters['edit_mode'] = $this->EditModeToPHP($sEditMode); + } + break; + case 'target_class': + $aParameters['targetclass'] = $this->GetPropString($oField, 'target_class', $default); + break; + case 'mappings': + $oMappings = $oField->GetUniqueElement('mappings'); + $oMappingNodes = $oMappings->getElementsByTagName('mapping'); + $aMapping = array(); + foreach ($oMappingNodes as $oMapping) { + $sMappingId = $oMapping->getAttribute('id'); + $sMappingAttCode = $oMapping->GetChildText('attcode'); + $aMapping[$sMappingId]['attcode'] = $sMappingAttCode; + $aMapping[$sMappingId]['values'] = array(); + $oMetaValues = $oMapping->GetUniqueElement('metavalues'); + foreach ($oMetaValues->getElementsByTagName('metavalue') as $oMetaValue) { + $sMetaValue = $oMetaValue->getAttribute('id'); + $oValues = $oMetaValue->GetUniqueElement('values'); + foreach ($oValues->getElementsByTagName('value') as $oValue) { + $sValue = $oValue->getAttribute('id'); + $aMapping[$sMappingId]['values'][$sValue] = $sMetaValue; + } + + } + } + $aParameters['mapping'] = var_export($aMapping, true); + break; + case 'default_image': + if (($sDefault = $oField->GetChildText('default_image')) && (strlen($sDefault) > 0)) { + $aParameters['default_image'] = "utils::GetAbsoluteUrlModulesRoot().'$sModuleRelativeDir/$sDefault'"; + } else { + $aParameters['default_image'] = 'null'; + } + break; + case 'states': + $oStates = $oField->GetUniqueElement('states'); + $oStateNodes = $oStates->getElementsByTagName('state'); + $aStates = array(); + foreach ($oStateNodes as $oState) { + $aStates[] = '"'.$oState->GetAttribute('id').'"'; + } + $aParameters['states'] = 'array('.implode(', ', $aStates).')'; + break; + case 'goal': + $aParameters['goal_computing'] = $this->GetPropString($oField, 'goal', $default); // Optional, no deadline by default + break; + case 'working_time': + $aParameters['working_time_computing'] = $this->GetPropString($oField, 'working_time', $default); // Blank (different than DefaultWorkingTimeComputer) + break; + case 'thresholds': + $oThresholds = $oField->GetUniqueElement('thresholds'); + $oThresholdNodes = $oThresholds->getElementsByTagName('threshold'); + $aThresholds = array(); + foreach ($oThresholdNodes as $oThreshold) { + $iPercent = (int)$oThreshold->getAttribute('id'); + + $oHighlight = $oThreshold->GetUniqueElement('highlight', false); + $sHighlight = ''; + if ($oHighlight) { + $sCode = $oHighlight->GetChildText('code'); + $sPersistent = $this->GetPropBoolean($oHighlight, 'persistent', false); + $sHighlight = "'highlight' => array('code' => '$sCode', 'persistent' => $sPersistent), "; + } + + $oActions = $oThreshold->GetUniqueElement('actions'); + $oActionNodes = $oActions->getElementsByTagName('action'); + $aActions = array(); + foreach ($oActionNodes as $oAction) { + $oParams = $oAction->GetOptionalElement('params'); + $aActionParams = array(); + if ($oParams) { + $oParamNodes = $oParams->getElementsByTagName('param'); + foreach ($oParamNodes as $oParam) { + $sParamType = $oParam->getAttribute('xsi:type'); + if ($sParamType == '') { + $sParamType = 'string'; + } + $aActionParams[] = "array('type' => '$sParamType', 'value' => ".self::QuoteForPHP($oParam->textContent).')'; + } + } + $sActionParams = 'array('.implode(', ', $aActionParams).')'; + $sVerb = $this->GetPropString($oAction, 'verb'); + $aActions[] = "array('verb' => $sVerb, 'params' => $sActionParams)"; + } + $sActions = 'array('.implode(', ', $aActions).')'; + $aThresholds[] = $iPercent." => array('percent' => $iPercent, $sHighlight 'actions' => $sActions)"; + } + $aParameters['thresholds'] = 'array('.implode(', ', $aThresholds).')'; + break; + + default: + return false; + } + + return true; + } + + /** + * @param string $sAttType + * @param \DOMElement $oField + * @param array $aParameters + * @param string $sDependencies + * + * @throws \DOMFormatException + * @since 3.1.0 + */ + protected function CompileDynamicAttribute(string $sAttType, DOMElement $oField, array &$aParameters, string $sModuleRelativeDir, string $sClass, string &$sCss, string $sDependencies): void + { + foreach ($this->GetPropertiesForDynamicAttributeDefinition($sAttType) as $sPropertyName => $aProperty) { + if (!$this->CompileCommonProperty($sPropertyName, $oField, $aParameters, $sModuleRelativeDir)) { + $sPHPParam = $aProperty['php_param']; + $bMandatory = $aProperty['mandatory']; + $sType = $aProperty['type']; + $sDefault = $aProperty['default']; + switch ($sType) { + case 'string': + if ($bMandatory) { + $aParameters[$sPHPParam] = $this->GetMandatoryPropString($oField, $sPropertyName); + } else { + $aParameters[$sPHPParam] = $this->GetPropString($oField, $sPropertyName, $sDefault); + } + break; + case 'boolean': + if ($bMandatory) { + $aParameters[$sPHPParam] = $this->GetMandatoryPropBoolean($oField, $sPropertyName); + } else { + $aParameters[$sPHPParam] = $this->GetPropBoolean($oField, $sPropertyName, $sDefault === 'true'); + } + break; + case 'number': + if ($bMandatory) { + $aParameters[$sPHPParam] = $this->GetMandatoryPropNumber($oField, $sPropertyName); + } else { + $aParameters[$sPHPParam] = $this->GetPropNumber($oField, $sPropertyName, (int)$sDefault); + } + break; + case 'php': + $sValue = $oField->GetChildText($sPropertyName); + if ($bMandatory && is_null($sValue)) { + throw new DOMFormatException("missing (or empty) mandatory tag '$sPropertyName' under the tag '".$oField->nodeName."'"); + } + $aParameters[$sPHPParam] = $sValue ?? 'null'; + break; + case 'oql': + if ($sOql = $oField->GetChildText($sPropertyName)) { + $sEscapedOql = self::QuoteForPHP($sOql); + $aParameters[$sPHPParam] = "$sEscapedOql"; + } else { + $aParameters[$sPHPParam] = 'null'; + } + break; + } + } + } + $aParameters['depends_on'] = $sDependencies; + } + /** * @internal This method is public in order to be used in the tests * * @param \MFElement $oNode Style node, can be either a