mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
4762-menu compilation rework after brainstorming
This commit is contained in:
@@ -103,7 +103,7 @@ class ApplicationMenu
|
||||
{
|
||||
self::$sFavoriteSiloQuery = $sOQL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the query used to limit the list of displayed organizations in the drop-down menu
|
||||
* @return string The OQL query returning a list of Organization objects
|
||||
@@ -273,12 +273,23 @@ class ApplicationMenu
|
||||
continue;
|
||||
}
|
||||
|
||||
$aSubMenuNodes = static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams);
|
||||
if (! MFCompiler::$bUseLegacyMenuCompilation && !($oMenuNode instanceof ShortcutMenuNode)){
|
||||
if (is_array($aSubMenuNodes) && 0 === sizeof($aSubMenuNodes)){
|
||||
IssueLog::Error('Empty menu node not displayed', LogChannels::CONSOLE, [
|
||||
'menu_node_class' => get_class($oMenuNode),
|
||||
'menu_node_label' => $oMenuNode->GetLabel(),
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$aMenuGroups[] = [
|
||||
'sId' => $oMenuNode->GetMenuID(),
|
||||
'sIconCssClasses' => $oMenuNode->GetDecorationClasses(),
|
||||
'sInitials' => $oMenuNode->GetInitials(),
|
||||
'sTitle' => $oMenuNode->GetTitle(),
|
||||
'aSubMenuNodes' => static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams),
|
||||
'aSubMenuNodes' => $aSubMenuNodes,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -525,7 +536,7 @@ EOF
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the currently active menu (if any, otherwise the first menu is the default)
|
||||
* @return string The Id of the currently active menu
|
||||
@@ -533,7 +544,7 @@ EOF
|
||||
public static function GetActiveNodeId()
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
|
||||
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
|
||||
if ($sMenuId === null)
|
||||
{
|
||||
$sMenuId = self::GetDefaultMenuId();
|
||||
@@ -643,7 +654,7 @@ abstract class MenuNode
|
||||
|
||||
/**
|
||||
* Stimulus to check: if the user can 'apply' this stimulus, then she/he can see this menu
|
||||
*/
|
||||
*/
|
||||
protected $m_aEnableStimuli;
|
||||
|
||||
/**
|
||||
@@ -804,7 +815,7 @@ abstract class MenuNode
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a limiting display condition for the same menu node. The conditions will be combined with a AND
|
||||
* @param $oMenuNode MenuNode Another definition of the same menu node, with potentially different access restriction
|
||||
@@ -977,7 +988,7 @@ class TemplateMenuNode extends MenuNode
|
||||
* @var string
|
||||
*/
|
||||
protected $sTemplateFile;
|
||||
|
||||
|
||||
/**
|
||||
* Create a menu item based on a custom template and inserts it into the application's main menu
|
||||
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
|
||||
@@ -1048,7 +1059,7 @@ class OQLMenuNode extends MenuNode
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $bSearchFormOpen;
|
||||
|
||||
|
||||
/**
|
||||
* Extra parameters to be passed to the display block to fine tune its appearence
|
||||
*/
|
||||
@@ -1081,7 +1092,7 @@ class OQLMenuNode extends MenuNode
|
||||
// Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects
|
||||
// of the class specified by the OQL...
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set some extra parameters to be passed to the display block to fine tune its appearence
|
||||
* @param array $aParams paramCode => value. See DisplayBlock::GetDisplay for the meaning of the parameters
|
||||
@@ -1109,7 +1120,7 @@ class OQLMenuNode extends MenuNode
|
||||
'Menu_'.$this->GetMenuId(),
|
||||
$this->bSearch, // Search pane
|
||||
$this->bSearchFormOpen, // Search open
|
||||
$oPage,
|
||||
$oPage,
|
||||
array_merge($this->m_aParams, $aExtraParams),
|
||||
true
|
||||
);
|
||||
@@ -1343,10 +1354,10 @@ class NewObjectMenuNode extends MenuNode
|
||||
{
|
||||
// Enable this menu, only if the current user has enough rights to create such an object, or an object of
|
||||
// any child class
|
||||
|
||||
|
||||
$aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
|
||||
$bActionIsAllowed = false;
|
||||
|
||||
|
||||
foreach($aSubClasses as $sCandidateClass)
|
||||
{
|
||||
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
|
||||
@@ -1355,7 +1366,7 @@ class NewObjectMenuNode extends MenuNode
|
||||
break; // Enough for now
|
||||
}
|
||||
}
|
||||
return $bActionIsAllowed;
|
||||
return $bActionIsAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1497,7 +1508,7 @@ class DashboardMenuNode extends MenuNode
|
||||
throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1538,7 +1549,7 @@ class ShortcutContainerMenuNode extends MenuNode
|
||||
$sName = $this->GetMenuId().'_'.$oShortcut->GetKey();
|
||||
new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++);
|
||||
}
|
||||
|
||||
|
||||
// Complete the tree
|
||||
//
|
||||
parent::PopulateChildMenus();
|
||||
|
||||
@@ -333,14 +333,6 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'set_menu_compilation_algorithm' => [
|
||||
'type' => 'string',
|
||||
'description' => 'setup menu compilation algorithm version (N°4762)',
|
||||
'default' => 'v1',
|
||||
'value' => 'v1',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'allow_target_creation' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Displays the + button on external keys to create target objects',
|
||||
|
||||
@@ -24,7 +24,6 @@ use Combodo\iTop\DesignElement;
|
||||
require_once(APPROOT.'setup/setuputils.class.inc.php');
|
||||
require_once(APPROOT.'setup/modelfactory.class.inc.php');
|
||||
require_once(APPROOT.'core/moduledesign.class.inc.php');
|
||||
require_once(APPROOT.'setup/parentmenunodecompiler.class.inc.php');
|
||||
|
||||
class DOMFormatException extends Exception
|
||||
{
|
||||
@@ -91,6 +90,8 @@ class MFCompiler
|
||||
*/
|
||||
const REBUILD_HKEYS_NEVER = APPROOT.'data/.setup-rebuild-hkeys-never';
|
||||
|
||||
public static $bUseLegacyMenuCompilation = false;
|
||||
|
||||
/** @var \ModelFactory */
|
||||
protected $oFactory;
|
||||
|
||||
@@ -127,6 +128,10 @@ class MFCompiler
|
||||
$this->aClassesCSSRules = [];
|
||||
}
|
||||
|
||||
public static function UseLegacyMenuCompilation(){
|
||||
self::$bUseLegacyMenuCompilation = true;
|
||||
}
|
||||
|
||||
protected function Log($sText)
|
||||
{
|
||||
$this->aLog[] = $sText;
|
||||
@@ -274,11 +279,7 @@ class MFCompiler
|
||||
|
||||
try
|
||||
{
|
||||
if (! is_null($oConfig) && $oConfig->Get('set_menu_compilation_algorithm') === 'v2'){
|
||||
$this->DoNewCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks);
|
||||
} else {
|
||||
$this->DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks);
|
||||
}
|
||||
$this->DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
@@ -510,7 +511,7 @@ EOF;
|
||||
{
|
||||
throw new Exception("Module '{$oModule->GetId()}' (location : '$sModuleRootDir') contains an unknown menuId : '$sMenuId'");
|
||||
}
|
||||
if ($oMenuNode->getAttribute("xsi:type") == 'MenuGroup')
|
||||
if (self::$bUseLegacyMenuCompilation && $oMenuNode->getAttribute("xsi:type") == 'MenuGroup')
|
||||
{
|
||||
// Note: this algorithm is wrong
|
||||
// 1 - the module may appear empty in the current module, while children are defined in other modules
|
||||
@@ -724,483 +725,6 @@ EOF
|
||||
|
||||
} // DoCompile()
|
||||
|
||||
/**
|
||||
* @since 3.0.x N°4762
|
||||
* Perform the enhanced "Compilation" of all modules
|
||||
* @param string $sTempTargetDir
|
||||
* @param string $sFinalTargetDir
|
||||
* @param Page $oP
|
||||
* @param bool $bUseSymbolicLinks
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function DoNewCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks = false)
|
||||
{
|
||||
$aAllClasses = []; // flat list of classes
|
||||
$aModulesInfo = []; // Hash array of module_name => array('version' => string, 'root_dir' => string)
|
||||
|
||||
// Determine the target modules for the MENUS
|
||||
//
|
||||
$aMenuNodes = [];
|
||||
$aMenusByModule = [];
|
||||
foreach ($this->oFactory->GetNodes('menus/menu') as $oMenuNode)
|
||||
{
|
||||
$sMenuId = $oMenuNode->getAttribute('id');
|
||||
$aMenuNodes[$sMenuId] = $oMenuNode;
|
||||
|
||||
$sModuleMenu = $oMenuNode->getAttribute('_created_in');
|
||||
$aMenusByModule[$sModuleMenu][] = $sMenuId;
|
||||
}
|
||||
|
||||
// Determine the target module (exactly one!) for USER RIGHTS
|
||||
// This used to be based solely on the module which created the user_rights node first
|
||||
// Unfortunately, our sample extension was delivered with the xml structure, resulting in the new module to be the recipient of the compilation
|
||||
// Then model.itop-profiles-itil would not exist... resulting in an error after the compilation (and the actual product of the compiler would never be included
|
||||
// The bullet proof implementation would be to compile in a separate directory as it has been done with the dictionaries... that's another story
|
||||
$aModules = $this->oFactory->GetLoadedModules();
|
||||
$sUserRightsModule = '';
|
||||
foreach ($aModules as $foo => $oModule) {
|
||||
if ($oModule->GetName() == 'itop-profiles-itil') {
|
||||
$sUserRightsModule = 'itop-profiles-itil';
|
||||
break;
|
||||
}
|
||||
}
|
||||
$oUserRightsNode = $this->oFactory->GetNodes('user_rights')->item(0);
|
||||
if ($oUserRightsNode && ($sUserRightsModule == '')) {
|
||||
// Legacy algorithm (itop <= 2.0.3)
|
||||
$sUserRightsModule = $oUserRightsNode->getAttribute('_created_in');
|
||||
}
|
||||
$this->Log("User Rights module found: '$sUserRightsModule'");
|
||||
|
||||
// List root classes
|
||||
//
|
||||
$this->aRootClasses = [];
|
||||
foreach ($this->oFactory->ListRootClasses() as $oClass) {
|
||||
$this->Log("Root class (with child classes): ".$oClass->getAttribute('id'));
|
||||
$this->aRootClasses[$oClass->getAttribute('id')] = $oClass;
|
||||
}
|
||||
|
||||
$this->LoadSnippets();
|
||||
|
||||
// Compile, module by module
|
||||
//
|
||||
$aModules = $this->oFactory->GetLoadedModules();
|
||||
$aDataModelFiles = [];
|
||||
$aWebservicesFiles = [];
|
||||
$iStart = strlen(realpath(APPROOT));
|
||||
$sRelFinalTargetDir = substr($sFinalTargetDir, strlen(APPROOT));
|
||||
|
||||
$this->WriteStaticOnlyHtaccess($sTempTargetDir);
|
||||
$this->WriteStaticOnlyWebConfig($sTempTargetDir);
|
||||
|
||||
static::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
|
||||
|
||||
$aParentModuleRootDirs = [];
|
||||
$aParentMenuNodes = [];
|
||||
foreach ($aModules as $foo => $oModule) {
|
||||
$sModuleName = $oModule->GetName();
|
||||
$sModuleVersion = $oModule->GetVersion();
|
||||
|
||||
$sModuleRootDir = $oModule->GetRootDir();
|
||||
if ($sModuleRootDir != '') {
|
||||
$sModuleRootDir = realpath($sModuleRootDir);
|
||||
$sRelativeDir = basename($sModuleRootDir);
|
||||
if ($bUseSymbolicLinks) {
|
||||
$sRealRelativeDir = substr($sModuleRootDir, $iStart);
|
||||
} else {
|
||||
$sRealRelativeDir = $sRelFinalTargetDir.'/'.$sRelativeDir;
|
||||
}
|
||||
|
||||
// Push the other module files
|
||||
SetupUtils::copydir($sModuleRootDir, $sTempTargetDir.'/'.$sRelativeDir, $bUseSymbolicLinks);
|
||||
} else {
|
||||
$sRelativeDir = $sModuleName;
|
||||
$sRealRelativeDir = $sModuleName;
|
||||
}
|
||||
$aModulesInfo[$sModuleName] = array('root_dir' => $sRealRelativeDir, 'version' => $sModuleVersion);
|
||||
|
||||
$sCompiledCode = '';
|
||||
|
||||
$oConstants = $this->oFactory->ListConstants($sModuleName);
|
||||
if ($oConstants->length > 0)
|
||||
{
|
||||
foreach($oConstants as $oConstant)
|
||||
{
|
||||
$sCompiledCode .= $this->CompileConstant($oConstant)."\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists($sModuleName, $this->aSnippets))
|
||||
{
|
||||
foreach( $this->aSnippets[$sModuleName]['before'] as $aSnippet)
|
||||
{
|
||||
$sCompiledCode .= "\n";
|
||||
$sCompiledCode .= "/**\n";
|
||||
$sCompiledCode .= " * Snippet: {$aSnippet['snippet_id']}\n";
|
||||
$sCompiledCode .= " */\n";
|
||||
$sCompiledCode .= $aSnippet['content']."\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$oClasses = $this->oFactory->ListClasses($sModuleName);
|
||||
$iClassCount = $oClasses->length;
|
||||
if ($iClassCount == 0)
|
||||
{
|
||||
$this->Log("Found module without classes declared: $sModuleName");
|
||||
}
|
||||
else
|
||||
{
|
||||
/** @var \MFElement $oClass */
|
||||
foreach($oClasses as $oClass)
|
||||
{
|
||||
$sClass = $oClass->getAttribute("id");
|
||||
$aAllClasses[] = $sClass;
|
||||
try
|
||||
{
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
}
|
||||
catch (DOMFormatException $e)
|
||||
{
|
||||
$sMessage = "Failed to process class '$sClass', ";
|
||||
if (!empty($sModuleRootDir)) {
|
||||
$sMessage .= "from '$sModuleRootDir': ";
|
||||
}
|
||||
$sMessage .= $e->getMessage();
|
||||
throw new Exception($sMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists($sModuleName, $aMenusByModule))
|
||||
{
|
||||
$this->Log("Found module without menus declared: $sModuleName");
|
||||
}
|
||||
else
|
||||
{
|
||||
$sMenuCreationClass = 'MenuCreation_'.preg_replace('/[^A-Za-z0-9_]/', '_', $sModuleName);
|
||||
$sCompiledCode .=
|
||||
<<<EOF
|
||||
|
||||
//
|
||||
// Menus
|
||||
//
|
||||
class $sMenuCreationClass extends ModuleHandlerAPI
|
||||
{
|
||||
public static function OnMenuCreation()
|
||||
{
|
||||
global \$__comp_menus__; // ensure that the global variable is indeed global !
|
||||
|
||||
EOF;
|
||||
// Preliminary: determine parent menus not defined within the current module
|
||||
$aMenusToLoad = array();
|
||||
foreach($aMenusByModule[$sModuleName] as $sMenuId)
|
||||
{
|
||||
$oMenuNode = $aMenuNodes[$sMenuId];
|
||||
if ($sParent = $oMenuNode->GetChildText('parent', null))
|
||||
{
|
||||
$aMenusToLoad[] = $sParent;
|
||||
if (!array_key_exists($sParent, $aParentModuleRootDirs)){
|
||||
$aParentModuleRootDirs[$sParent] = $sModuleRootDir;
|
||||
}
|
||||
}
|
||||
// Note: the order matters: the parents must be defined BEFORE
|
||||
$aMenusToLoad[] = $sMenuId;
|
||||
}
|
||||
$aMenusToLoad = array_unique($aMenusToLoad);
|
||||
$aMenuLinesForAll = [];
|
||||
$aMenuLinesForAdmins = [];
|
||||
$aAdminMenus = [];
|
||||
foreach($aMenusToLoad as $sMenuId)
|
||||
{
|
||||
$oMenuNode = $aMenuNodes[$sMenuId];
|
||||
if (is_null($oMenuNode))
|
||||
{
|
||||
throw new Exception("Module '{$oModule->GetId()}' (location : '$sModuleRootDir') contains an unknown menuId : '$sMenuId'");
|
||||
}
|
||||
if ($oMenuNode->getAttribute("xsi:type") == 'MenuGroup' || array_key_exists($sMenuId, $aParentModuleRootDirs))
|
||||
{
|
||||
$aParentMenuNodes[$sMenuId] = $oMenuNode;
|
||||
$sParent = $oMenuNode->GetChildText('parent', null);
|
||||
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($aAdminMenus[$sParent]))
|
||||
{
|
||||
$aAdminMenus[$sMenuId] = true;
|
||||
}
|
||||
continue;
|
||||
|
||||
// Note: this algorithm is wrong
|
||||
// 1 - the module may appear empty in the current module, while children are defined in other modules
|
||||
// 2 - check recursively that child nodes are not empty themselves
|
||||
// Future algorithm:
|
||||
// a- browse the modules and build the menu tree
|
||||
// b- browse the tree and blacklist empty menus
|
||||
// c- before compiling, discard if blacklisted
|
||||
}
|
||||
try
|
||||
{
|
||||
$aMenuLines = $this->CompileMenu($oMenuNode, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
||||
}
|
||||
catch (DOMFormatException $e)
|
||||
{
|
||||
throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
|
||||
}
|
||||
$sParent = $oMenuNode->GetChildText('parent', null);
|
||||
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($aAdminMenus[$sParent]))
|
||||
{
|
||||
$aMenuLinesForAdmins = array_merge($aMenuLinesForAdmins, $aMenuLines);
|
||||
$aAdminMenus[$oMenuNode->getAttribute("id")] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aMenuLinesForAll = array_merge($aMenuLinesForAll, $aMenuLines);
|
||||
}
|
||||
}
|
||||
$sIndent = "\t\t";
|
||||
foreach ($aMenuLinesForAll as $sPHPLine)
|
||||
{
|
||||
$sCompiledCode .= $sIndent.$sPHPLine."\n";
|
||||
}
|
||||
if (count($aMenuLinesForAdmins) > 0)
|
||||
{
|
||||
$sCompiledCode .= $sIndent."if (UserRights::IsAdministrator())\n";
|
||||
$sCompiledCode .= $sIndent."{\n";
|
||||
foreach ($aMenuLinesForAdmins as $sPHPLine)
|
||||
{
|
||||
$sCompiledCode .= $sIndent."\t".$sPHPLine."\n";
|
||||
}
|
||||
$sCompiledCode .= $sIndent."}\n";
|
||||
}
|
||||
$sCompiledCode .=
|
||||
<<<EOF
|
||||
}
|
||||
} // class $sMenuCreationClass
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
// User rights
|
||||
//
|
||||
if ($sModuleName == $sUserRightsModule)
|
||||
{
|
||||
$sCompiledCode .= $this->CompileUserRights($oUserRightsNode);
|
||||
}
|
||||
|
||||
if (array_key_exists($sModuleName, $this->aSnippets))
|
||||
{
|
||||
foreach( $this->aSnippets[$sModuleName]['after'] as $aSnippet)
|
||||
{
|
||||
$sCompiledCode .= "\n";
|
||||
$sCompiledCode .= "/**\n";
|
||||
$sCompiledCode .= " * Snippet: {$aSnippet['snippet_id']}\n";
|
||||
$sCompiledCode .= " */\n";
|
||||
$sCompiledCode .= $aSnippet['content']."\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Create (overwrite if existing) the compiled file
|
||||
//
|
||||
if (strlen($sCompiledCode) > 0)
|
||||
{
|
||||
// We have compiled something: write the code somewhere
|
||||
//
|
||||
if (strlen($sModuleRootDir) > 0)
|
||||
{
|
||||
// Write the code into the given module as model.<module>.php
|
||||
//
|
||||
$sResultFile = $sTempTargetDir.'/'.$sRelativeDir.'/model.'.$sModuleName.'.php';
|
||||
$this->WritePHPFile($sResultFile, $sModuleName, $sModuleVersion, $sCompiledCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write the code into core/main.php
|
||||
//
|
||||
$this->sMainPHPCode .=
|
||||
<<<EOF
|
||||
/**
|
||||
* Data model from the delta file
|
||||
*/
|
||||
|
||||
EOF;
|
||||
$this->sMainPHPCode .= $sCompiledCode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->Log("Compilation of module $sModuleName in version $sModuleVersion produced not code at all. No file written.");
|
||||
}
|
||||
|
||||
// files to include (PHP datamodels)
|
||||
foreach($oModule->GetFilesToInclude('business') as $sRelFileName)
|
||||
{
|
||||
$aDataModelFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');";
|
||||
}
|
||||
// files to include (PHP webservices providers)
|
||||
foreach($oModule->GetFilesToInclude('webservices') as $sRelFileName)
|
||||
{
|
||||
$aWebservicesFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');";
|
||||
}
|
||||
} // foreach module
|
||||
|
||||
// Compile the dictionaries -out of the modules
|
||||
//
|
||||
$sDictDir = $sTempTargetDir.'/dictionaries';
|
||||
if (!is_dir($sDictDir))
|
||||
{
|
||||
$this->Log("Creating directory $sDictDir");
|
||||
mkdir($sDictDir, 0777, true);
|
||||
}
|
||||
|
||||
$oDictionaries = $this->oFactory->GetNodes('dictionaries/dictionary');
|
||||
$this->CompileDictionaries($oDictionaries, $sTempTargetDir, $sFinalTargetDir);
|
||||
|
||||
// Compile the branding
|
||||
//
|
||||
$oBrandingNode = $this->oFactory->GetNodes('branding')->item(0);
|
||||
$this->CompileBranding($oBrandingNode, $sTempTargetDir, $sFinalTargetDir);
|
||||
|
||||
if (array_key_exists('_core_', $this->aSnippets))
|
||||
{
|
||||
foreach( $this->aSnippets['_core_']['before'] as $aSnippet)
|
||||
{
|
||||
$this->sMainPHPCode .= "\n";
|
||||
$this->sMainPHPCode .= "/**\n";
|
||||
$this->sMainPHPCode .= " * Snippet: {$aSnippet['snippet_id']}\n";
|
||||
$this->sMainPHPCode .= " */\n";
|
||||
$this->sMainPHPCode .= $aSnippet['content']."\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Compile the portals
|
||||
$oPortalsNode = $this->oFactory->GetNodes('/itop_design/portals')->item(0);
|
||||
$this->CompilePortals($oPortalsNode, $sTempTargetDir, $sFinalTargetDir);
|
||||
|
||||
// Create module design XML files
|
||||
$oModuleDesignsNode = $this->oFactory->GetNodes('/itop_design/module_designs')->item(0);
|
||||
$this->CompileModuleDesigns($oModuleDesignsNode, $sTempTargetDir, $sFinalTargetDir);
|
||||
|
||||
// Compile the XML parameters
|
||||
$oParametersNode = $this->oFactory->GetNodes('/itop_design/module_parameters')->item(0);
|
||||
$this->CompileParameters($oParametersNode, $sTempTargetDir, $sFinalTargetDir);
|
||||
|
||||
if (array_key_exists('_core_', $this->aSnippets))
|
||||
{
|
||||
foreach( $this->aSnippets['_core_']['after'] as $aSnippet)
|
||||
{
|
||||
$this->sMainPHPCode .= "\n";
|
||||
$this->sMainPHPCode .= "/**\n";
|
||||
$this->sMainPHPCode .= " * Snippet: {$aSnippet['snippet_id']}\n";
|
||||
$this->sMainPHPCode .= " */\n";
|
||||
$this->sMainPHPCode .= $aSnippet['content']."\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (count($this->aRelations) > 0)
|
||||
{
|
||||
$this->sMainPHPCode .= "\n";
|
||||
$this->sMainPHPCode .= "/**\n";
|
||||
$this->sMainPHPCode .= " * Relations\n";
|
||||
$this->sMainPHPCode .= " */\n";
|
||||
foreach($this->aRelations as $sRelationCode => $aData)
|
||||
{
|
||||
$sRelCodeSafe = addslashes($sRelationCode);
|
||||
$this->sMainPHPCode .= "MetaModel::RegisterRelation('$sRelCodeSafe');\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Write core/main.php
|
||||
SetupUtils::builddir($sTempTargetDir.'/core');
|
||||
$sPHPFile = $sTempTargetDir.'/core/main.php';
|
||||
file_put_contents($sPHPFile, $this->sMainPHPCode);
|
||||
|
||||
$this->GenerateMenuNodePhpCode($aParentModuleRootDirs, $aParentMenuNodes, $aAdminMenus, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
||||
|
||||
$sCurrDate = date(DATE_ISO8601);
|
||||
// Autoload
|
||||
$sPHPFile = $sTempTargetDir.'/autoload.php';
|
||||
$sPHPFileContent =
|
||||
<<<EOF
|
||||
<?php
|
||||
//
|
||||
// File generated on $sCurrDate
|
||||
// Please do not edit manually
|
||||
//
|
||||
EOF
|
||||
;
|
||||
|
||||
$sPHPFileContent .= "\nMetaModel::IncludeModule(MODULESROOT.'/core/parent_menus.php');\n";
|
||||
$sPHPFileContent .= "MetaModel::IncludeModule(MODULESROOT.'/core/main.php');\n";
|
||||
|
||||
$sPHPFileContent .= implode("\n", $aDataModelFiles);
|
||||
$sPHPFileContent .= implode("\n", $aWebservicesFiles);
|
||||
$sModulesInfo = var_export($aModulesInfo, true);
|
||||
$sModulesInfo = str_replace("'".$sRelFinalTargetDir."/", "\$sCurrEnv.'/", $sModulesInfo);
|
||||
$sPHPFileContent .= "\nfunction GetModulesInfo()\n{\n\$sCurrEnv = 'env-'.utils::GetCurrentEnvironment();\nreturn ".$sModulesInfo.";\n}\n";
|
||||
file_put_contents($sPHPFile, $sPHPFileContent);
|
||||
|
||||
} // DoNewCompile()
|
||||
|
||||
/**
|
||||
* @since 3.0.x N°4762
|
||||
*/
|
||||
private function GenerateMenuNodePhpCode(array $aParentModuleRootDirs, array $aParentMenuNodes, array $aAdminMenus,
|
||||
string $sTempTargetDir, string $sFinalTargetDir, string $sRelativeDir, ?Page $oP = null) : void
|
||||
{
|
||||
$oCompileParentMenuNode = new ParentMenuNodeCompiler($this, $aParentModuleRootDirs, $aParentMenuNodes, $aAdminMenus,
|
||||
$sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
||||
|
||||
//Handle menu nodes
|
||||
foreach ($aParentModuleRootDirs as $sMenuId => $sModuleRootDir) {
|
||||
$oCompileParentMenuNode->CompileParentMenuNode($sMenuId);
|
||||
}
|
||||
|
||||
$aMenuLinesForAdmins = $oCompileParentMenuNode->GetMenuLinesForAdmins();
|
||||
$aMenuLinesForAll = $oCompileParentMenuNode->GetMenuLinesForAll();
|
||||
|
||||
$sCurrDate = date(DATE_ISO8601);
|
||||
$sCompiledCode =
|
||||
<<<PHP
|
||||
<?php
|
||||
//
|
||||
// File generated on $sCurrDate
|
||||
// Please do not edit manually
|
||||
//
|
||||
//
|
||||
// Menus
|
||||
//
|
||||
class ParentMenusCreation extends ModuleHandlerAPI
|
||||
{
|
||||
public static function OnMenuCreation()
|
||||
{
|
||||
global \$__comp_menus__; // ensure that the global variable is indeed global !
|
||||
|
||||
PHP;
|
||||
|
||||
$sIndent = "\t\t";
|
||||
foreach ($aMenuLinesForAll as $sPHPLine)
|
||||
{
|
||||
$sCompiledCode .= $sIndent.$sPHPLine."\n";
|
||||
}
|
||||
if (count($aMenuLinesForAdmins) > 0)
|
||||
{
|
||||
$sCompiledCode .= $sIndent."if (UserRights::IsAdministrator())\n";
|
||||
$sCompiledCode .= $sIndent."{\n";
|
||||
foreach ($aMenuLinesForAdmins as $sPHPLine)
|
||||
{
|
||||
$sCompiledCode .= $sIndent."\t".$sPHPLine."\n";
|
||||
}
|
||||
$sCompiledCode .= $sIndent."}\n";
|
||||
}
|
||||
|
||||
$sCompiledCode .= <<<PHP
|
||||
|
||||
}
|
||||
} // class ParentMenusCreation
|
||||
PHP;
|
||||
|
||||
|
||||
$sPHPFile = $sTempTargetDir.'/core/parent_menus.php';
|
||||
file_put_contents($sPHPFile, $sCompiledCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to form a valid ZList from the array built by GetNodeAsArrayOfItems()
|
||||
*
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @since 3.0.x N°4762
|
||||
*/
|
||||
class ParentMenuNodeCompiler
|
||||
{
|
||||
/**
|
||||
* @var MFCompiler
|
||||
*/
|
||||
|
||||
private $oMFCompiler;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aParentModuleRootDirs;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aParentMenuNodes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aAdminMenus;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sTempTargetDir;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sFinalTargetDir;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sRelativeDir;
|
||||
|
||||
/**
|
||||
* @var Page|null
|
||||
*/
|
||||
private $oP;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aMenuLinesForAdmins = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aMenuLinesForAll = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aMenuProcessStatus = [];
|
||||
|
||||
const COMPILED = 1;
|
||||
const COMPILING = 2;
|
||||
|
||||
public function __construct(MFCompiler $oMFCompiler, array $aParentModuleRootDirs, array $aParentMenuNodes, array $aAdminMenus,
|
||||
string $sTempTargetDir, string $sFinalTargetDir, string $sRelativeDir, ?Page $oP = null) {
|
||||
$this->oMFCompiler = $oMFCompiler;
|
||||
$this->aParentModuleRootDirs = $aParentModuleRootDirs;
|
||||
$this->aParentMenuNodes = $aParentMenuNodes;
|
||||
$this->aAdminMenus = $aAdminMenus;
|
||||
$this->sTempTargetDir = $sTempTargetDir;
|
||||
$this->sFinalTargetDir = $sFinalTargetDir;
|
||||
$this->sRelativeDir = $sRelativeDir;
|
||||
$this->oP = $oP;
|
||||
}
|
||||
|
||||
public function CompileParentMenuNode(string $sMenuId) : void
|
||||
{
|
||||
$sStatus = array_key_exists($sMenuId, $this->aMenuProcessStatus) ? $this->aMenuProcessStatus[$sMenuId] : null;
|
||||
if ($sStatus === self::COMPILED){
|
||||
//node already processed before
|
||||
return;
|
||||
} else if ($sStatus === self::COMPILING){
|
||||
throw new \Exception("Cyclic dependency between parent menus ($sMenuId)");
|
||||
}
|
||||
|
||||
$this->aMenuProcessStatus[$sMenuId] = self::COMPILING;
|
||||
|
||||
try {
|
||||
if (! array_key_exists($sMenuId, $this->aParentMenuNodes)){
|
||||
throw new Exception("Failed to process parent menu '$sMenuId' that is referenced by a child but not defined");
|
||||
}
|
||||
$oMenuNode = $this->aParentMenuNodes[$sMenuId];
|
||||
|
||||
$sParent = $oMenuNode->GetChildText('parent', null);
|
||||
if (! empty($sParent)){
|
||||
//compile parents before (even parent of parents ... recursively)
|
||||
$this->CompileParentMenuNode($sParent);
|
||||
}
|
||||
|
||||
if (! array_key_exists($sMenuId, $this->aParentModuleRootDirs)){
|
||||
throw new Exception("Failed to process parent menu '$sMenuId' that is referenced by a child but not defined");
|
||||
}
|
||||
$sModuleRootDir = $this->aParentModuleRootDirs[$sMenuId];
|
||||
|
||||
$aMenuLines = $this->oMFCompiler->CompileMenu($oMenuNode, $this->sTempTargetDir, $this->sFinalTargetDir, $this->sRelativeDir, $this->oP);
|
||||
} catch (DOMFormatException $e) {
|
||||
throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
|
||||
}
|
||||
$sParent = $oMenuNode->GetChildText('parent', null);
|
||||
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($this->aAdminMenus[$sParent])) {
|
||||
$this->aMenuLinesForAdmins = array_merge($this->aMenuLinesForAdmins, $aMenuLines);
|
||||
$this->aAdminMenus[$oMenuNode->getAttribute("id")] = true;
|
||||
} else {
|
||||
$this->aMenuLinesForAll = array_merge($this->aMenuLinesForAll, $aMenuLines);
|
||||
}
|
||||
|
||||
$this->aMenuProcessStatus[$sMenuId] = self::COMPILED;
|
||||
}
|
||||
|
||||
public function GetMenuLinesForAdmins(): array {
|
||||
return $this->aMenuLinesForAdmins;
|
||||
}
|
||||
|
||||
public function GetMenuLinesForAll(): array {
|
||||
return $this->aMenuLinesForAll;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,11 +19,14 @@ use RunTimeEnvironment;
|
||||
*/
|
||||
class MFCompilerMenuTest extends ItopTestCase {
|
||||
private static $aPreviousEnvMenus;
|
||||
private static $aPreviousEnvMenuCount;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
require_once(APPROOT.'setup/compiler.class.inc.php');
|
||||
require_once(APPROOT.'setup/modelfactory.class.inc.php');
|
||||
require_once(APPROOT.'application/utils.inc.php');
|
||||
|
||||
}
|
||||
|
||||
public function tearDown(): void {
|
||||
@@ -32,40 +35,37 @@ class MFCompilerMenuTest extends ItopTestCase {
|
||||
|
||||
public function CompileMenusProvider(){
|
||||
return [
|
||||
'production' => ['production'],
|
||||
'phpunit' => ['phpunit'],
|
||||
'legacy_algo' => [ 'sEnv' => 'legacy_algo', 'bLegacyMenuCompilation' => true ],
|
||||
'menu_compilation_fix' => [ 'sEnv' => 'menu_compilation_fix', 'bLegacyMenuCompilation' => false ],
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @dataProvider CompileMenusProvider
|
||||
*/
|
||||
public function testCompileMenus($sEnv){
|
||||
if(\utils::GetCurrentEnvironment() != $sEnv) {
|
||||
$sConfigFilePath = \utils::GetConfigFilePath($sEnv);
|
||||
public function testCompileMenus($sEnv, $bLegacyMenuCompilation){
|
||||
$sConfigFilePath = \utils::GetConfigFilePath($sEnv);
|
||||
|
||||
//copy conf from production to phpunit context
|
||||
$sDirPath = dirname($sConfigFilePath);
|
||||
if (! is_dir($sDirPath)){
|
||||
mkdir($sDirPath);
|
||||
}
|
||||
$oConfig = new Config(\utils::GetConfigFilePath());
|
||||
$oConfig->WriteToFile($sConfigFilePath);
|
||||
|
||||
$oConfig = new Config($sConfigFilePath);
|
||||
$oConfig->Set('set_menu_compilation_algorithm', 'v2', 'test', true);
|
||||
$oConfig->WriteToFile();
|
||||
$oRunTimeEnvironment = new RunTimeEnvironment($sEnv);
|
||||
$oRunTimeEnvironment->CompileFrom(\utils::GetCurrentEnvironment());
|
||||
$oConfig->Set('set_menu_compilation_algorithm', 'v1', 'test', true);
|
||||
$oConfig->WriteToFile();
|
||||
//copy conf from production to phpunit context
|
||||
$sDirPath = dirname($sConfigFilePath);
|
||||
if (! is_dir($sDirPath)){
|
||||
mkdir($sDirPath);
|
||||
}
|
||||
$oConfig = new Config(\utils::GetConfigFilePath());
|
||||
$oConfig->WriteToFile($sConfigFilePath);
|
||||
|
||||
$oConfig = new Config($sConfigFilePath);
|
||||
if ($bLegacyMenuCompilation){
|
||||
MFCompiler::UseLegacyMenuCompilation();
|
||||
}
|
||||
$oConfig->WriteToFile();
|
||||
$oRunTimeEnvironment = new RunTimeEnvironment($sEnv);
|
||||
$oRunTimeEnvironment->CompileFrom(\utils::GetCurrentEnvironment());
|
||||
$oConfig->WriteToFile();
|
||||
|
||||
$sConfigFile = APPCONF.\utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
|
||||
$aMenuGroups = ApplicationMenu::GetMenuGroups();
|
||||
$this->assertNotEquals([], $aMenuGroups);
|
||||
|
||||
if (! is_null(static::$aPreviousEnvMenus)){
|
||||
$this->assertEquals(static::$aPreviousEnvMenus, $aMenuGroups);
|
||||
} else {
|
||||
@@ -73,6 +73,13 @@ class MFCompilerMenuTest extends ItopTestCase {
|
||||
}
|
||||
static::$aPreviousEnvMenus = $aMenuGroups;
|
||||
|
||||
//$this->InvokeNonPublicMethod(MFCompiler::class, 'CompileThemes', $this->oMFCompiler, [$oBrandingNode, $this->sTmpDir]);
|
||||
$aMenuCount = ApplicationMenu::GetMenusCount();
|
||||
|
||||
if (! is_null(static::$aPreviousEnvMenuCount)){
|
||||
$this->assertEquals(static::$aPreviousEnvMenuCount, $aMenuCount);
|
||||
} else {
|
||||
$this->assertNotEquals([], $aMenuCount);
|
||||
}
|
||||
static::$aPreviousEnvMenuCount = $aMenuCount;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user