4762-menu compilation rework after brainstorming

This commit is contained in:
odain
2023-02-28 14:54:26 +01:00
parent 60b08586c2
commit 0533916dad
5 changed files with 64 additions and 680 deletions

View File

@@ -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();

View File

@@ -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',

View File

@@ -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()
*

View File

@@ -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;
}
}

View File

@@ -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;
}
}