diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 06965f6c37..2b7a9f3a22 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -1150,9 +1150,20 @@ class Config { return $this->m_aModuleSettings[$sModule][$sProperty]; } - return $defaultvalue; + // Fall back to the predefined XML parameter, if any + return $this->GetModuleParameter($sModule, $sProperty, $defaultvalue); } + public function GetModuleParameter($sModule, $sProperty, $defaultvalue = null) + { + $aAllParams = ModulesXMLParameters::GetData($sModule); + if(array_key_exists($sProperty, $aAllParams)) + { + return $aAllParams[$sProperty]; + } + return $defaultvalue; + } + public function SetModuleSetting($sModule, $sProperty, $value) { $this->m_aModuleSettings[$sModule][$sProperty] = $value; diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 1c4be393f6..677fd37710 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -40,6 +40,9 @@ class MFCompiler $this->aLog = array(); $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"; + $this->sMainPHPCode .= " */\n"; } protected function Log($sText) @@ -384,6 +387,10 @@ EOF; $oPortalsNode = $this->oFactory->GetNodes('/itop_design/portals')->item(0); $this->CompilePortals($oPortalsNode, $sTempTargetDir, $sFinalTargetDir); + // Compile the XML parameters + $oParametersNode = $this->oFactory->GetNodes('/itop_design/module_parameters')->item(0); + $this->CompileParameters($oParametersNode, $sTempTargetDir, $sFinalTargetDir); + // Write core/main.php SetupUtils::builddir($sTempTargetDir.'/core'); $sPHPFile = $sTempTargetDir.'/core/main.php'; @@ -1977,9 +1984,13 @@ EOF; uasort($aPortalsConfig, array(get_class($this), 'SortOnRank')); + $this->sMainPHPCode .= "\n"; + $this->sMainPHPCode .= "/**\n"; + $this->sMainPHPCode .= " * Portal(s) definition(s) extracted from the XML definition at compile time\n"; + $this->sMainPHPCode .= " */\n"; $this->sMainPHPCode .= "class PortalDispatcherData\n"; $this->sMainPHPCode .= "{\n"; - $this->sMainPHPCode .= "\tprotected static \$aData = ".str_replace("\n", "\n\t", var_export($aPortalsConfig, true)).";\n\n"; + $this->sMainPHPCode .= "\tprotected static \$aData = ".var_export($aPortalsConfig, true).";\n\n"; $this->sMainPHPCode .= "\tpublic static function GetData(\$sPortalId = null)\n"; $this->sMainPHPCode .= "\t{\n"; $this->sMainPHPCode .= "\t\tif (\$sPortalId === null) return self::\$aData;\n"; @@ -1989,11 +2000,42 @@ EOF; $this->sMainPHPCode .= "}\n"; } } - + public static function SortOnRank($aConf1, $aConf2) { return ($aConf1['rank'] < $aConf2['rank']) ? -1 : 1; } + + protected function CompileParameters($oParametersNode, $sTempTargetDir, $sFinalTargetDir) + { + if ($oParametersNode) + { + // Create some static PHP data in /core/main.php + $oParameters = $oParametersNode->GetNodes('parameters'); + $aParametersConfig = array(); + foreach($oParameters as $oParams) + { + $sModuleId = $oParams->getAttribute('id'); + $oParamsReader = new MFParameters($oParams); + $aParametersConfig[$sModuleId] = $oParamsReader->GetAll(); + } + + $this->sMainPHPCode .= "\n"; + $this->sMainPHPCode .= "/**\n"; + $this->sMainPHPCode .= " * Modules parameters extracted from the XML definition at compile time\n"; + $this->sMainPHPCode .= " */\n"; + $this->sMainPHPCode .= "class ModulesXMLParameters\n"; + $this->sMainPHPCode .= "{\n"; + $this->sMainPHPCode .= "\tprotected static \$aData = ".var_export($aParametersConfig, true).";\n\n"; + $this->sMainPHPCode .= "\tpublic static function GetData(\$sModuleId = null)\n"; + $this->sMainPHPCode .= "\t{\n"; + $this->sMainPHPCode .= "\t\tif (\$sModuleId === null) return self::\$aData;\n"; + $this->sMainPHPCode .= "\t\tif (!array_key_exists(\$sModuleId, self::\$aData)) return array();\n"; + $this->sMainPHPCode .= "\t\treturn self::\$aData[\$sModuleId];\n"; + $this->sMainPHPCode .= "\t}\n"; + $this->sMainPHPCode .= "}\n"; + } + } } ?> diff --git a/setup/modelfactory.class.inc.php b/setup/modelfactory.class.inc.php index af19d6bfa4..c3d789c33f 100644 --- a/setup/modelfactory.class.inc.php +++ b/setup/modelfactory.class.inc.php @@ -2079,4 +2079,241 @@ class MFDocument extends DOMDocument return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc; } +} + +/** + * Helper class manage parameters stored as XML nodes + * to be converted to a PHP structure during compilation + * Values can be either a hash, an array, a string, a boolean, an int or a float + */ +class MFParameters +{ + protected $aData = null; + + public function __construct(DOMNode $oNode) + { + $this->aData = array(); + $this->LoadFromDOM($oNode); + } + + public function Get($sCode, $default = '') + { + if (array_key_exists($sCode, $this->aData)) + { + return $this->aData[$sCode]; + } + return $default; + } + + public function GetAll() + { + return $this->aData; + } + + public function LoadFromDOM(DOMNode $oNode) + { + $this->aData = array(); + foreach($oNode->childNodes as $oChildNode) + { + if ($oChildNode instanceof DOMElement) + { + $this->aData[$oChildNode->nodeName] = $this->ReadElement($oChildNode); + } + } + } + + protected function ReadElement(DOMNode $oNode) + { + if ($oNode instanceof DOMElement) + { + $sDefaultNodeType = ($this->HasChildNodes($oNode)) ? 'hash' : 'string'; + $sNodeType = $oNode->getAttribute('type'); + if ($sNodeType == '') + { + $sNodeType = $sDefaultNodeType; + } + + switch($sNodeType) + { + case 'array': + $value = array(); + // Treat the current element as zero based array, child tag names are NOT meaningful + $sFirstTagName = null; + foreach($oNode->childNodes as $oChildElement) + { + if ($oChildElement instanceof DOMElement) + { + if ($sFirstTagName == null) + { + $sFirstTagName = $oChildElement->nodeName; + } + else if ($sFirstTagName != $oChildElement->nodeName) + { + throw new Exception("Invalid Parameters: mixed tags ('$sFirstTagName' and '".$oChildElement->nodeName."') inside array '".$oNode->nodeName."'"); + } + $val = $this->ReadElement($oChildElement); + $idx = (string)$oChildElement->getAttribute('id'); // Don't cast into float, since floats are converted to int (i.e. truncated) when used as hash indexes (cf: http://php.net/manual/en/language.types.array.php) + if ($idx !== '') + { + $value[$idx] = $val; + } + else + { + // No specific Id, just push the value at the end of the array + $value[] = $val; + } + } + } + ksort($value, SORT_NUMERIC); + break; + + case 'hash': + $value = array(); + // Treat the current element as a hash, child tag names are keys + foreach($oNode->childNodes as $oChildElement) + { + if ($oChildElement instanceof DOMElement) + { + if (array_key_exists($oChildElement->nodeName, $value)) + { + throw new Exception("Invalid Parameters file: duplicate tags '".$oChildElement->nodeName."' inside hash '".$oNode->nodeName."'"); + } + $val = $this->ReadElement($oChildElement); + $value[$oChildElement->nodeName] = $val; + } + } + break; + + case 'int': + case 'integer': + $value = (int)$this->GetText($oNode); + break; + + case 'bool': + case 'boolean': + if (($this->GetText($oNode) == 'true') || ($this->GetText($oNode) == 1)) + { + $value = true; + } + else + { + $value = false; + } + break; + + case 'string': + default: + $value = str_replace('\n', "\n", (string)$this->GetText($oNode)); + } + } + else if ($oNode instanceof DOMText) + { + $value = $oNode->wholeText; + } + return $value; + } + + protected function GetAttribute($sAttName, $oNode, $sDefaultValue) + { + $sRet = $sDefaultValue; + + foreach($oNode->attributes as $oAttribute) + { + if ($oAttribute->nodeName == $sAttName) + { + $sRet = $oAttribute->nodeValue; + break; + } + } + return $sRet; + } + + /** + * Returns the TEXT of the current node (possibly from several subnodes) + */ + public function GetText($oNode, $sDefault = null) + { + $sText = null; + foreach($oNode->childNodes as $oChildNode) + { + if ($oChildNode instanceof DOMText) + { + if (is_null($sText)) $sText = ''; + $sText .= $oChildNode->wholeText; + } + } + if (is_null($sText)) + { + return $sDefault; + } + else + { + return $sText; + } + } + /** + * Check if a node as child nodes (apart from text nodes) + */ + public function HasChildNodes($oNode) + { + if ($oNode instanceof DOMElement) + { + foreach($oNode->childNodes as $oChildNode) + { + if ($oChildNode instanceof DOMElement) + { + return true; + } + } + } + return false; + } + function Merge(XMLParameters $oTask) + { + $this->aData = $this->array_merge_recursive_distinct($this->aData, $oTask->aData); + } + + /** + * array_merge_recursive does indeed merge arrays, but it converts values with duplicate + * keys to arrays rather than overwriting the value in the first array with the duplicate + * value in the second array, as array_merge does. I.e., with array_merge_recursive, + * this happens (documented behavior): + * + * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value')); + * => array('key' => array('org value', 'new value')); + * + * array_merge_recursive_distinct does not change the datatypes of the values in the arrays. + * Matching keys' values in the second array overwrite those in the first array, as is the + * case with array_merge, i.e.: + * + * array_merge_recursive_distinct(array('key' => 'org value'), array('key' => 'new value')); + * => array('key' => array('new value')); + * + * Parameters are passed by reference, though only for performance reasons. They're not + * altered by this function. + * + * @param array $array1 + * @param array $array2 + * @return array + * @author Daniel + * @author Gabriel Sobrinho + */ + protected function array_merge_recursive_distinct ( array &$array1, array &$array2 ) + { + $merged = $array1; + + foreach ( $array2 as $key => &$value ) + { + if ( is_array ( $value ) && isset ( $merged [$key] ) && is_array ( $merged [$key] ) ) + { + $merged [$key] = $this->array_merge_recursive_distinct ( $merged [$key], $value ); + } + else + { + $merged [$key] = $value; + } + } + + return $merged; + } } \ No newline at end of file