mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 15:34:12 +01:00
Before this fix, when creating a module for iTop that was creating PHP code (an iTop class for example, a snippet, ...) it was mandatory to add the model.php file in your module.php file ("datamodel" key). If you forgot this, then the compilation was completed OK but the result code wasn't included in iTop.
Now the compiler automatically adds the model.php file to the included files.
3803 lines
127 KiB
PHP
3803 lines
127 KiB
PHP
<?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
|
|
*/
|
|
|
|
|
|
use Combodo\iTop\Application\Branding;
|
|
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');
|
|
|
|
|
|
class DOMFormatException extends Exception
|
|
{
|
|
/**
|
|
* Overrides the Exception default constructor to automatically add informations about the concerned node (path and
|
|
* line number)
|
|
*
|
|
* @param string $message
|
|
* @param $code
|
|
* @param $previous
|
|
* @param DOMNode $node [Optionnal] DOMNode causing the DOMFormatException
|
|
*/
|
|
public function __construct($message, $code = null, $previous = null, DOMNode $node = null)
|
|
{
|
|
if($node !== null)
|
|
{
|
|
$message .= ' ('.MFDocument::GetItopNodePath($node).' at line '.$node->getLineNo().')';
|
|
}
|
|
parent::__construct($message, $code, $previous);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compiler class
|
|
*/
|
|
class MFCompiler
|
|
{
|
|
const DATA_PRECOMPILED_FOLDER = 'data'.DIRECTORY_SEPARATOR.'precompiled_styles'.DIRECTORY_SEPARATOR;
|
|
|
|
/**
|
|
* @var string
|
|
* @see self::GenerateStyleDataFromNode
|
|
* @internal
|
|
* @since 3.0.0
|
|
*/
|
|
public const ENUM_STYLE_HOST_ELEMENT_TYPE_CLASS = 'class';
|
|
/**
|
|
* @var string
|
|
* @see self::GenerateStyleDataFromNode
|
|
* @internal
|
|
* @since 3.0.0
|
|
*/
|
|
public const ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM = 'enum';
|
|
|
|
/**
|
|
* Path to the "use symlinks" file
|
|
* If this file is present, then we will compile to symlink !
|
|
*
|
|
* @var string
|
|
*
|
|
* @since 3.0.0 N°4092
|
|
*/
|
|
public const USE_SYMBOLIC_LINKS_FILE_PATH = APPROOT.'data/.compilation-symlinks';
|
|
|
|
/** @var \ThemeHandlerService */
|
|
protected static $oThemeHandlerService;
|
|
|
|
/**
|
|
* Path to the "calculate hKeys" file
|
|
* If this file is present, then we don't recalculate hkeys
|
|
*
|
|
* @var string
|
|
* @since 2.7.5 3.0.0 N°4020
|
|
*/
|
|
const REBUILD_HKEYS_NEVER = APPROOT.'data/.setup-rebuild-hkeys-never';
|
|
|
|
/** @var \ModelFactory */
|
|
protected $oFactory;
|
|
|
|
protected $aRootClasses;
|
|
protected $aLog;
|
|
protected $sMainPHPCode; // Code that goes into core/main.php
|
|
protected $aSnippets;
|
|
protected $aRelations;
|
|
/**
|
|
* @var array Strings containing dynamic CSS definitions for DM classes
|
|
* @since 3.0.0
|
|
*/
|
|
protected $aClassesCSSRules;
|
|
protected $sEnvironment;
|
|
protected $sCompilationTimeStamp;
|
|
|
|
public function __construct($oModelFactory, $sEnvironment)
|
|
{
|
|
$this->oFactory = $oModelFactory;
|
|
$this->sEnvironment = $sEnvironment;
|
|
|
|
$this->oFactory->ApplyChanges();
|
|
|
|
$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";
|
|
$this->sMainPHPCode .= "\n";
|
|
$this->sCompilationTimeStamp = "".microtime(true);
|
|
$this->sMainPHPCode .= "define('COMPILATION_TIMESTAMP', '".$this->sCompilationTimeStamp."');\n";
|
|
$this->aSnippets = array();
|
|
$this->aRelations = array();
|
|
$this->aClassesCSSRules = [];
|
|
}
|
|
|
|
protected function Log($sText)
|
|
{
|
|
$this->aLog[] = $sText;
|
|
}
|
|
|
|
protected function DumpLog($oPage)
|
|
{
|
|
foreach ($this->aLog as $sText) {
|
|
$oPage->p($sText);
|
|
}
|
|
}
|
|
|
|
public function GetLog()
|
|
{
|
|
return $this->aLog;
|
|
}
|
|
|
|
/**
|
|
* @return bool if flag is present true, false otherwise
|
|
*
|
|
* @uses \file_exists()
|
|
* @uses USE_SYMBOLIC_LINKS_FILE_PATH
|
|
*
|
|
* @since 3.0.0 N°4092
|
|
*/
|
|
public static function IsUseSymbolicLinksFlagPresent(): bool
|
|
{
|
|
return (file_exists(static::USE_SYMBOLIC_LINKS_FILE_PATH));
|
|
}
|
|
|
|
/**
|
|
* This is to check if the functionality can be used. As this is really only useful for developers,
|
|
* this is strictly limited and not available on any iTop instance !
|
|
*
|
|
* @return bool Check that the symlinks flag can be used
|
|
* * always false if not in dev env
|
|
* * `symlink` function non-existent : false
|
|
* * true otherwise
|
|
*
|
|
* @uses utils::IsDevelopmentEnvironment()
|
|
* @uses \function_exists()
|
|
*
|
|
* @since 3.0.0 N°4092
|
|
*/
|
|
public static function CanUseSymbolicLinksFlagBeUsed(): bool
|
|
{
|
|
if (false === utils::IsDevelopmentEnvironment()) {
|
|
return false;
|
|
}
|
|
|
|
if (false === function_exists('symlink')) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param bool $bUseSymbolicLinks
|
|
*
|
|
* @uses USE_SYMBOLIC_LINKS_FILE_PATH
|
|
*
|
|
* @since 3.0.0 N°4092
|
|
*/
|
|
public static function SetUseSymbolicLinksFlag(bool $bUseSymbolicLinks): void
|
|
{
|
|
$bIsUseSymlinksFlagPresent = (static::IsUseSymbolicLinksFlagPresent());
|
|
|
|
if ($bUseSymbolicLinks) {
|
|
if ($bIsUseSymlinksFlagPresent) {
|
|
return;
|
|
}
|
|
|
|
touch(static::USE_SYMBOLIC_LINKS_FILE_PATH);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!$bIsUseSymlinksFlagPresent) {
|
|
return;
|
|
}
|
|
unlink(static::USE_SYMBOLIC_LINKS_FILE_PATH);
|
|
}
|
|
|
|
/**
|
|
* @return bool possible return values :
|
|
* * if flag is present true, false otherwise
|
|
*
|
|
* @uses \file_exists()
|
|
* @uses REBUILD_HKEYS_NEVER
|
|
*
|
|
* @since 2.7.5 3.0.0
|
|
*/
|
|
public static function SkipRebuildHKeys()
|
|
{
|
|
return (file_exists(static::REBUILD_HKEYS_NEVER));
|
|
}
|
|
|
|
/**
|
|
* Compile the data model into PHP files and data structures
|
|
*
|
|
* @param string $sTargetDir The target directory where to put the resulting files
|
|
* @param Page $oP For some output...
|
|
* @param bool $bUseSymbolicLinks
|
|
* @param bool $bSkipTempDir
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function Compile($sTargetDir, $oP = null, $bUseSymbolicLinks = null, $bSkipTempDir = false)
|
|
{
|
|
if (is_null($bUseSymbolicLinks)) {
|
|
$bUseSymbolicLinks = false;
|
|
if (self::CanUseSymbolicLinksFlagBeUsed() && self::IsUseSymbolicLinksFlagPresent()) {
|
|
// We are only overriding the useSymLinks option if the consumer didn't specify anything
|
|
// The toolkit always send this parameter for example, but not the Designer Connector
|
|
$bUseSymbolicLinks = true;
|
|
}
|
|
}
|
|
|
|
$sFinalTargetDir = $sTargetDir;
|
|
$bIsAlreadyInMaintenanceMode = SetupUtils::IsInMaintenanceMode();
|
|
$sConfigFilePath = utils::GetConfigFilePath($this->sEnvironment);
|
|
if (is_file($sConfigFilePath)) {
|
|
$oConfig = new Config($sConfigFilePath);
|
|
} else {
|
|
$oConfig = null;
|
|
}
|
|
if (($this->sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) {
|
|
|
|
SetupUtils::EnterMaintenanceMode($oConfig);
|
|
}
|
|
if ($bUseSymbolicLinks || $bSkipTempDir) {
|
|
// Skip the creation of a temporary dictionary, not compatible with symbolic links
|
|
$sTempTargetDir = $sFinalTargetDir;
|
|
SetupUtils::rrmdir($sFinalTargetDir);
|
|
SetupUtils::builddir($sFinalTargetDir); // Here is the directory
|
|
} else {
|
|
// Create a temporary directory
|
|
// Once the compilation is 100% successful, then move the results into the target directory
|
|
$sTempTargetDir = tempnam(SetupUtils::GetTmpDir(), 'itop-');
|
|
unlink($sTempTargetDir); // I need a directory, not a file...
|
|
SetupUtils::builddir($sTempTargetDir); // Here is the directory
|
|
}
|
|
|
|
try
|
|
{
|
|
$this->DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks);
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
if ($sTempTargetDir != $sFinalTargetDir)
|
|
{
|
|
// Cleanup the temporary directory
|
|
SetupUtils::rrmdir($sTempTargetDir);
|
|
}
|
|
if (($this->sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode)
|
|
{
|
|
SetupUtils::ExitMaintenanceMode();
|
|
}
|
|
throw $e;
|
|
}
|
|
|
|
if ($sTempTargetDir != $sFinalTargetDir)
|
|
{
|
|
// Move the results to the target directory
|
|
SetupUtils::movedir($sTempTargetDir, $sFinalTargetDir);
|
|
}
|
|
if (($this->sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode)
|
|
{
|
|
SetupUtils::ExitMaintenanceMode();
|
|
}
|
|
|
|
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
|
|
// In case of bad luck (this happens **sometimes** - see N. 550), we may analyze the database structure
|
|
// with the previous datamodel still loaded (in opcode cache) and thus fail to create the new fields
|
|
// Finally the application crashes (because of the missing field) when the cache gets updated
|
|
if (function_exists('opcache_reset'))
|
|
{
|
|
// Zend opcode cache
|
|
opcache_reset();
|
|
}
|
|
if (function_exists('apc_clear_cache'))
|
|
{
|
|
// old style APC
|
|
apc_clear_cache();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Perform the actual "Compilation" of all modules
|
|
* @param string $sTempTargetDir
|
|
* @param string $sFinalTargetDir
|
|
* @param Page $oP
|
|
* @param bool $bUseSymbolicLinks
|
|
* @throws Exception
|
|
*/
|
|
protected function DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks = false)
|
|
{
|
|
$aAllClasses = array(); // flat list of classes
|
|
$aModulesInfo = array(); // Hash array of module_name => array('version' => string, 'root_dir' => string)
|
|
|
|
// Determine the target modules for the MENUS
|
|
//
|
|
$aMenuNodes = array();
|
|
$aMenusByModule = array();
|
|
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 = array();
|
|
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 = array();
|
|
$aWebservicesFiles = array();
|
|
$iStart = strlen(realpath(APPROOT));
|
|
$sRelFinalTargetDir = substr($sFinalTargetDir, strlen(APPROOT));
|
|
|
|
$this->WriteStaticOnlyHtaccess($sTempTargetDir);
|
|
$this->WriteStaticOnlyWebConfig($sTempTargetDir);
|
|
|
|
static::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
|
|
|
|
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();
|
|
$aParentMenus = array();
|
|
foreach($aMenusByModule[$sModuleName] as $sMenuId)
|
|
{
|
|
$oMenuNode = $aMenuNodes[$sMenuId];
|
|
if ($sParent = $oMenuNode->GetChildText('parent', null))
|
|
{
|
|
$aMenusToLoad[] = $sParent;
|
|
$aParentMenus[] = $sParent;
|
|
}
|
|
// Note: the order matters: the parents must be defined BEFORE
|
|
$aMenusToLoad[] = $sMenuId;
|
|
}
|
|
$aMenusToLoad = array_unique($aMenusToLoad);
|
|
$aMenuLinesForAll = array();
|
|
$aMenuLinesForAdmins = array();
|
|
$aAdminMenus = array();
|
|
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')
|
|
{
|
|
// 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
|
|
if (!in_array($oMenuNode->getAttribute("id"), $aParentMenus))
|
|
{
|
|
// Discard empty menu groups
|
|
continue;
|
|
}
|
|
}
|
|
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
|
|
//
|
|
$sModelFileName = 'model.'.$sModuleName.'.php';
|
|
$sResultFile = $sTempTargetDir.'/'.$sRelativeDir.'/'.$sModelFileName;
|
|
$this->WritePHPFile($sResultFile, $sModuleName, $sModuleVersion, $sCompiledCode);
|
|
// In case the model file wasn't present in the module file, we're adding it ! (N°4875)
|
|
$oModule->AddFileToInclude('business', $sModelFileName);
|
|
}
|
|
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)
|
|
{
|
|
if (file_exists("{$sTempTargetDir}/{$sRelativeDir}/{$sRelFileName}")) {
|
|
$aDataModelFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');";
|
|
} else {
|
|
/** @noinspection NestedPositiveIfStatementsInspection */
|
|
if (utils::IsDevelopmentEnvironment()) {
|
|
$sMissingBusinessFileMessage = 'A module embeds a non existing file : check the module.php "datamodel" key !';
|
|
$aContext = [
|
|
'moduleId' => $oModule->GetId(),
|
|
'moduleLocation' => $oModule->GetRootDir(),
|
|
'includedFile' => $sRelFileName,
|
|
];
|
|
SetupLog::Error($sMissingBusinessFileMessage, null, $aContext);
|
|
throw new CoreException($sMissingBusinessFileMessage, $aContext);
|
|
}
|
|
}
|
|
}
|
|
// 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);
|
|
|
|
$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/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);
|
|
|
|
} // DoCompile()
|
|
|
|
/**
|
|
* Helper to form a valid ZList from the array built by GetNodeAsArrayOfItems()
|
|
*
|
|
* @param array $aItems
|
|
*/
|
|
protected function ArrayOfItemsToZList(&$aItems)
|
|
{
|
|
// Note: $aItems can be null in some cases so we have to protect it otherwise a PHP warning will be thrown during the foreach
|
|
if(!is_array($aItems))
|
|
{
|
|
$aItems = array();
|
|
}
|
|
$aTransformed = array();
|
|
|
|
foreach ($aItems as $key => $value)
|
|
{
|
|
if (is_null($value))
|
|
{
|
|
$aTransformed[] = $key;
|
|
}
|
|
else
|
|
{
|
|
if (is_array($value))
|
|
{
|
|
$this->ArrayOfItemsToZList($value);
|
|
}
|
|
$aTransformed[$key] = $value;
|
|
}
|
|
}
|
|
$aItems = $aTransformed;
|
|
}
|
|
|
|
/**
|
|
* Helper to format the flags for an attribute, in a given state
|
|
* @param object $oAttNode DOM node containing the information to build the flags
|
|
* Returns string PHP flags, based on the OPT_ATT_ constants, or empty (meaning 0, can be omitted)
|
|
*/
|
|
protected function FlagsToPHP($oAttNode)
|
|
{
|
|
static $aNodeAttributeToFlag = array(
|
|
'mandatory' => 'OPT_ATT_MANDATORY',
|
|
'read_only' => 'OPT_ATT_READONLY',
|
|
'must_prompt' => 'OPT_ATT_MUSTPROMPT',
|
|
'must_change' => 'OPT_ATT_MUSTCHANGE',
|
|
'hidden' => 'OPT_ATT_HIDDEN',
|
|
);
|
|
|
|
$aFlags = array();
|
|
foreach ($aNodeAttributeToFlag as $sNodeAttribute => $sFlag)
|
|
{
|
|
$bFlag = ($oAttNode->GetOptionalElement($sNodeAttribute) != null);
|
|
if ($bFlag)
|
|
{
|
|
$aFlags[] = $sFlag;
|
|
}
|
|
}
|
|
if (empty($aFlags))
|
|
{
|
|
$aFlags[] = 'OPT_ATT_NORMAL'; // When no flag is defined, reset the state to "normal"
|
|
}
|
|
$sRes = implode(' | ', $aFlags);
|
|
return $sRes;
|
|
}
|
|
|
|
/**
|
|
* Helper to format the tracking level for linkset (direct or indirect attributes)
|
|
*
|
|
* @param string $sTrackingLevel Value set from within the XML
|
|
* Returns string PHP flag
|
|
*
|
|
* @throws \DOMFormatException
|
|
*/
|
|
protected function TrackingLevelToPHP($sAttType, $sTrackingLevel)
|
|
{
|
|
static $aXmlToPHP_Links = array(
|
|
'none' => 'LINKSET_TRACKING_NONE',
|
|
'list' => 'LINKSET_TRACKING_LIST',
|
|
'details' => 'LINKSET_TRACKING_DETAILS',
|
|
'all' => 'LINKSET_TRACKING_ALL',
|
|
);
|
|
|
|
static $aXmlToPHP_Others = array(
|
|
'none' => 'ATTRIBUTE_TRACKING_NONE',
|
|
'all' => 'ATTRIBUTE_TRACKING_ALL',
|
|
);
|
|
|
|
switch ($sAttType)
|
|
{
|
|
case 'AttributeLinkedSetIndirect':
|
|
case 'AttributeLinkedSet':
|
|
$aXmlToPHP = $aXmlToPHP_Links;
|
|
break;
|
|
|
|
default:
|
|
$aXmlToPHP = $aXmlToPHP_Others;
|
|
}
|
|
|
|
if (!array_key_exists($sTrackingLevel, $aXmlToPHP))
|
|
{
|
|
throw new DOMFormatException("Tracking level: unknown value '$sTrackingLevel', expecting a value in {".implode(', ', array_keys($aXmlToPHP))."}");
|
|
}
|
|
return $aXmlToPHP[$sTrackingLevel];
|
|
}
|
|
|
|
/**
|
|
* Helper to format the edit-mode for direct linkset
|
|
*
|
|
* @param string $sEditMode Value set from within the XML
|
|
* Returns string PHP flag
|
|
*
|
|
* @throws \DOMFormatException
|
|
*/
|
|
protected function EditModeToPHP($sEditMode)
|
|
{
|
|
static $aXmlToPHP = array(
|
|
'none' => 'LINKSET_EDITMODE_NONE',
|
|
'add_only' => 'LINKSET_EDITMODE_ADDONLY',
|
|
'actions' => 'LINKSET_EDITMODE_ACTIONS',
|
|
'in_place' => 'LINKSET_EDITMODE_INPLACE',
|
|
'add_remove' => 'LINKSET_EDITMODE_ADDREMOVE',
|
|
);
|
|
|
|
if (!array_key_exists($sEditMode, $aXmlToPHP))
|
|
{
|
|
throw new DOMFormatException("Edit mode: unknown value '$sEditMode'");
|
|
}
|
|
return $aXmlToPHP[$sEditMode];
|
|
}
|
|
|
|
|
|
/**
|
|
* Format a path (file or url) as an absolute path or relative to the module or the app
|
|
*/
|
|
protected function PathToPHP($sPath, $sModuleRelativeDir, $bIsUrl = false)
|
|
{
|
|
if ($sPath == '')
|
|
{
|
|
$sPHP = "''";
|
|
}
|
|
elseif (substr($sPath, 0, 2) == '$$')
|
|
{
|
|
// Absolute
|
|
$sPHP = self::QuoteForPHP(substr($sPath, 2));
|
|
}
|
|
elseif (substr($sPath, 0, 1) == '$')
|
|
{
|
|
// Relative to the application
|
|
if ($bIsUrl)
|
|
{
|
|
$sPHP = "utils::GetAbsoluteUrlAppRoot().".self::QuoteForPHP(substr($sPath, 1));
|
|
}
|
|
else
|
|
{
|
|
$sPHP = "APPROOT.".self::QuoteForPHP(substr($sPath, 1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Relative to the module
|
|
if ($bIsUrl)
|
|
{
|
|
$sPHP = "utils::GetAbsoluteUrlModulePage('$sModuleRelativeDir', ".self::QuoteForPHP($sPath).")";
|
|
}
|
|
else
|
|
{
|
|
$sPHP = "dirname(__FILE__).'/$sPath'";
|
|
}
|
|
}
|
|
return $sPHP;
|
|
}
|
|
|
|
/**
|
|
* @param $oNode
|
|
* @param string $sTag
|
|
* @param string|null $sDefault
|
|
* @param bool $bAddQuotes If true, surrounds property value with single quotes and escapes existing single quotes
|
|
*
|
|
* @return string|null
|
|
*
|
|
* @since 3.0.0 Add param. $bAddQuotes to be equivalent to {@see self::GetMandatoryPropString} and allow retrieving property without surrounding single quotes
|
|
*/
|
|
protected function GetPropString($oNode, string $sTag, string $sDefault = null, bool $bAddQuotes = true)
|
|
{
|
|
$val = $oNode->GetChildText($sTag);
|
|
if (is_null($val))
|
|
{
|
|
if (is_null($sDefault))
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
$val = $sDefault;
|
|
}
|
|
}
|
|
|
|
if ($bAddQuotes) {
|
|
$val = "'".str_replace("'", "\\'", $val)."'";
|
|
}
|
|
|
|
return $val;
|
|
}
|
|
|
|
/**
|
|
* @param $oNode
|
|
* @param string $sTag
|
|
* @param bool $bAddQuotes
|
|
*
|
|
* @return string
|
|
* @throws \DOMFormatException
|
|
*/
|
|
protected function GetMandatoryPropString($oNode, string $sTag, bool $bAddQuotes = true)
|
|
{
|
|
$val = $oNode->GetChildText($sTag);
|
|
if (!is_null($val) && ($val !== ''))
|
|
{
|
|
if ($bAddQuotes) {
|
|
return "'".$val."'";
|
|
} else {
|
|
return $val;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new DOMFormatException("missing (or empty) mandatory tag '$sTag' under the tag '".$oNode->nodeName."'");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $oNode
|
|
* @param $sTag
|
|
* @param bool|null $bDefault
|
|
*
|
|
* @return bool|null
|
|
*/
|
|
private function GetPropBooleanConverted($oNode, $sTag, $bDefault = null)
|
|
{
|
|
$sValue = $this->GetPropBoolean($oNode, $sTag, $bDefault);
|
|
|
|
if ($sValue == null)
|
|
{
|
|
return null;
|
|
}
|
|
if ($sValue == 'true')
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param $oNode
|
|
* @param $sTag
|
|
* @param bool|null $bDefault
|
|
*
|
|
* @return null|string
|
|
* @see GetPropBooleanConverted() to get boolean value
|
|
*/
|
|
protected function GetPropBoolean($oNode, $sTag, $bDefault = null)
|
|
{
|
|
$val = $oNode->GetChildText($sTag);
|
|
if (is_null($val))
|
|
{
|
|
if (is_null($bDefault))
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
return $bDefault ? 'true' : 'false';
|
|
}
|
|
}
|
|
return (($val == 'true') || ($val == '1')) ? 'true' : 'false';
|
|
}
|
|
|
|
/**
|
|
* @param $oNode
|
|
* @param $sTag
|
|
*
|
|
* @return string
|
|
* @throws \DOMFormatException
|
|
*/
|
|
protected function GetMandatoryPropBoolean($oNode, $sTag)
|
|
{
|
|
$val = $oNode->GetChildText($sTag);
|
|
if (is_null($val))
|
|
{
|
|
throw new DOMFormatException("missing (or empty) mandatory tag '$sTag' under the tag '".$oNode->nodeName."'");
|
|
}
|
|
return $val == 'true' ? 'true' : 'false';
|
|
}
|
|
|
|
protected function GetPropNumber($oNode, $sTag, $nDefault = null)
|
|
{
|
|
$val = $oNode->GetChildText($sTag);
|
|
if (is_null($val))
|
|
{
|
|
if (is_null($nDefault))
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
$val = $nDefault;
|
|
}
|
|
}
|
|
return (string)$val;
|
|
}
|
|
|
|
/**
|
|
* @param $oNode
|
|
* @param $sTag
|
|
*
|
|
* @return string
|
|
* @throws \DOMFormatException
|
|
*/
|
|
protected function GetMandatoryPropNumber($oNode, $sTag)
|
|
{
|
|
$val = $oNode->GetChildText($sTag);
|
|
if (is_null($val))
|
|
{
|
|
throw new DOMFormatException("missing (or empty) mandatory tag '$sTag' under the tag '".$oNode->nodeName."'");
|
|
}
|
|
return (string)$val;
|
|
}
|
|
|
|
/**
|
|
* Adds quotes and escape characters
|
|
*/
|
|
protected function QuoteForPHP($sStr, $bSimpleQuotes = false)
|
|
{
|
|
if ($bSimpleQuotes)
|
|
{
|
|
$sEscaped = str_replace(array('\\', "'"), array('\\\\', "\\'"), $sStr);
|
|
$sRet = "'$sEscaped'";
|
|
}
|
|
else
|
|
{
|
|
$sEscaped = str_replace(array('\\', '"', "\n"), array('\\\\', '\\"', '\\n'), $sStr);
|
|
$sRet = '"'.$sEscaped.'"';
|
|
}
|
|
return $sRet;
|
|
}
|
|
|
|
protected function CompileConstant($oConstant)
|
|
{
|
|
$sName = $oConstant->getAttribute('id');
|
|
$sType = $oConstant->getAttribute('xsi:type');
|
|
$sText = $oConstant->GetText(null);
|
|
|
|
switch ($sType)
|
|
{
|
|
case 'integer':
|
|
if (is_null($sText))
|
|
{
|
|
// No data given => null
|
|
$sScalar = 'null';
|
|
}
|
|
else
|
|
{
|
|
$sScalar = (string)(int)$sText;
|
|
}
|
|
break;
|
|
|
|
case 'float':
|
|
if (is_null($sText))
|
|
{
|
|
// No data given => null
|
|
$sScalar = 'null';
|
|
}
|
|
else
|
|
{
|
|
$sScalar = (string)(float)$sText;
|
|
}
|
|
break;
|
|
|
|
case 'bool':
|
|
if (is_null($sText))
|
|
{
|
|
// No data given => null
|
|
$sScalar = 'null';
|
|
}
|
|
else
|
|
{
|
|
$sScalar = ($sText == 'true') ? 'true' : 'false';
|
|
}
|
|
break;
|
|
|
|
case 'string':
|
|
default:
|
|
$sScalar = $this->QuoteForPHP($sText, true);
|
|
}
|
|
$sPHPDefine = "define('$sName', $sScalar);";
|
|
return $sPHPDefine;
|
|
}
|
|
|
|
/**
|
|
* @param \MFElement $oClass
|
|
* @param string $sTempTargetDir
|
|
* @param string $sFinalTargetDir
|
|
* @param string $sModuleRelativeDir
|
|
*
|
|
* @return string
|
|
* @throws \DOMFormatException
|
|
*/
|
|
protected function CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
|
{
|
|
$sClass = $oClass->getAttribute('id');
|
|
$oProperties = $oClass->GetUniqueElement('properties');
|
|
$sPHP = '';
|
|
/** @var Contains dynamic CSS class definitions $sCss */
|
|
$sCss = '';
|
|
|
|
// Class caracteristics
|
|
//
|
|
$aClassParams = array();
|
|
$aClassParams['category'] = $this->GetPropString($oProperties, 'category', '');
|
|
$aClassParams['key_type'] = "'autoincrement'";
|
|
if ((bool)$this->GetPropNumber($oProperties, 'is_link', 0)) {
|
|
$aClassParams['is_link'] = 'true';
|
|
}
|
|
|
|
// Naming
|
|
$sComplementaryNameAttCode = "";
|
|
if ($oNaming = $oProperties->GetOptionalElement('naming')) {
|
|
$oNameAttributes = $oNaming->GetUniqueElement('attributes');
|
|
/** @var \DOMNodeList $oAttributes */
|
|
$oAttributes = $oNameAttributes->getElementsByTagName('attribute');
|
|
$aNameAttCodes = array();
|
|
/** @var \MFElement $oAttribute */
|
|
foreach ($oAttributes as $oAttribute) {
|
|
$aNameAttCodes[] = $oAttribute->getAttribute('id');
|
|
}
|
|
if (count($aNameAttCodes) > 0) {
|
|
// New style...
|
|
$sNameAttCode = "array('".implode("', '", $aNameAttCodes)."')";
|
|
} else {
|
|
$sNameAttCode = "''";
|
|
}
|
|
if ($oComplementaryNameAttributes = $oNaming->GetOptionalElement('complementary_attributes')) {
|
|
/** @var \DOMNodeList $oAttributes */
|
|
$oComplementaryAttributes = $oComplementaryNameAttributes->getElementsByTagName('attribute');
|
|
$aComplementaryNameAttCodes = array();
|
|
/** @var \MFElement $oAttribute */
|
|
foreach ($oComplementaryAttributes as $oComplementaryAttribute) {
|
|
$aComplementaryNameAttCodes[] = $oComplementaryAttribute->getAttribute('id');
|
|
}
|
|
if (count($aComplementaryNameAttCodes) > 0) {
|
|
$sComplementaryNameAttCode = "array('".implode("', '", $aComplementaryNameAttCodes)."')";
|
|
}
|
|
$aClassParams['complementary_name_attcode'] = $sComplementaryNameAttCode;
|
|
}
|
|
} else {
|
|
$sNameAttCode = "''";
|
|
}
|
|
$aClassParams['name_attcode'] = $sNameAttCode;
|
|
|
|
// Semantic
|
|
// - Default attributes code
|
|
$sImageAttCode = "";
|
|
$sStateAttCode = "";
|
|
// - Parse optional fields semantic node
|
|
$oFieldsSemantic = $oProperties->GetOptionalElement('fields_semantic');
|
|
if ($oFieldsSemantic) {
|
|
// Image attribute
|
|
$oImageAttribute = $oFieldsSemantic->GetOptionalElement('image_attribute');
|
|
if ($oImageAttribute) {
|
|
$sImageAttCode = $oImageAttribute->GetText();
|
|
}
|
|
|
|
// State attribute (for XML v1.7- the lifecycle/attribute node should have been migrated in this one)
|
|
$oStateAttribute = $oFieldsSemantic->GetOptionalElement('state_attribute');
|
|
if ($oStateAttribute) {
|
|
$sStateAttCode = $oStateAttribute->GetText();
|
|
}
|
|
}
|
|
$aClassParams['image_attcode'] = "'$sImageAttCode'";
|
|
$aClassParams['state_attcode'] = "'$sStateAttCode'";
|
|
|
|
// Reconcialiation
|
|
if ($oReconciliation = $oProperties->GetOptionalElement('reconciliation')) {
|
|
$oReconcAttributes = $oReconciliation->getElementsByTagName('attribute');
|
|
$aReconcAttCodes = array();
|
|
foreach ($oReconcAttributes as $oAttribute) {
|
|
$aReconcAttCodes[] = $oAttribute->getAttribute('id');
|
|
}
|
|
if (empty($aReconcAttCodes)) {
|
|
$sReconcKeys = "array()";
|
|
} else {
|
|
$sReconcKeys = "array('".implode("', '", $aReconcAttCodes)."')";
|
|
}
|
|
} else {
|
|
$sReconcKeys = "array()";
|
|
}
|
|
$aClassParams['reconc_keys'] = $sReconcKeys;
|
|
|
|
$aClassParams['db_table'] = $this->GetPropString($oProperties, 'db_table', '');
|
|
$aClassParams['db_key_field'] = $this->GetPropString($oProperties, 'db_key_field', 'id');
|
|
|
|
if (array_key_exists($sClass, $this->aRootClasses)) {
|
|
$sDefaultFinalClass = 'finalclass';
|
|
} else {
|
|
$sDefaultFinalClass = '';
|
|
}
|
|
$aClassParams['db_finalclass_field'] = $this->GetPropString($oProperties, 'db_final_class_field', $sDefaultFinalClass);
|
|
|
|
$this->CompileFiles($oProperties, $sTempTargetDir.'/'.$sModuleRelativeDir, $sFinalTargetDir.'/'.$sModuleRelativeDir, '');
|
|
|
|
// Style
|
|
if ($oStyle = $oProperties->GetOptionalElement('style')) {
|
|
$aClassStyleData = $this->GenerateStyleDataFromNode($oStyle, $sModuleRelativeDir, self::ENUM_STYLE_HOST_ELEMENT_TYPE_CLASS, $sClass);
|
|
$aClassParams['style'] = $aClassStyleData['orm_style_instantiation'];
|
|
$sCss .= $aClassStyleData['scss'];
|
|
}
|
|
|
|
|
|
$oOrder = $oProperties->GetOptionalElement('order');
|
|
if ($oOrder) {
|
|
$oColumnsNode = $oOrder->GetUniqueElement('columns');
|
|
$oColumns = $oColumnsNode->getElementsByTagName('column');
|
|
$aSortColumns = array();
|
|
foreach ($oColumns as $oColumn) {
|
|
$aSortColumns[] = "'".$oColumn->getAttribute('id')."' => ".(($oColumn->getAttribute('ascending') == 'true') ? 'true' : 'false');
|
|
}
|
|
if (count($aSortColumns) > 0) {
|
|
$aClassParams['order_by_default'] = "array(".implode(", ", $aSortColumns).")";
|
|
}
|
|
}
|
|
|
|
if ($oIndexes = $oProperties->GetOptionalElement('indexes'))
|
|
{
|
|
$aIndexes = array();
|
|
foreach($oIndexes->getElementsByTagName('index') as $oIndex)
|
|
{
|
|
$sIndexId = $oIndex->getAttribute('id');
|
|
$oAttributes = $oIndex->GetUniqueElement('attributes');
|
|
foreach ($oAttributes->getElementsByTagName('attribute') as $oAttribute) {
|
|
$aIndexes[$sIndexId][] = $oAttribute->getAttribute('id');
|
|
}
|
|
}
|
|
$aClassParams['indexes'] = var_export($aIndexes, true);
|
|
}
|
|
|
|
if ($oArchive = $oProperties->GetOptionalElement('archive')) {
|
|
$bEnabled = $this->GetPropBoolean($oArchive, 'enabled', false);
|
|
$aClassParams['archive'] = $bEnabled;
|
|
}
|
|
|
|
if ($oObsolescence = $oProperties->GetOptionalElement('obsolescence')) {
|
|
$sCondition = trim($this->GetPropString($oObsolescence, 'condition', ''));
|
|
if ($sCondition != "''") {
|
|
$aClassParams['obsolescence_expression'] = $sCondition;
|
|
}
|
|
}
|
|
|
|
if ($oUniquenessRules = $oProperties->GetOptionalElement('uniqueness_rules')) {
|
|
$aUniquenessRules = array();
|
|
/** @var \MFElement $oUniquenessSingleRule */
|
|
foreach ($oUniquenessRules->GetElementsByTagName('rule') as $oUniquenessSingleRule) {
|
|
$sCurrentRuleId = $oUniquenessSingleRule->getAttribute('id');
|
|
|
|
$oAttributes = $oUniquenessSingleRule->GetUniqueElement('attributes', false);
|
|
if ($oAttributes) {
|
|
$aUniquenessAttributes = array();
|
|
foreach ($oAttributes->getElementsByTagName('attribute') as $oAttribute) {
|
|
$aUniquenessAttributes[] = $oAttribute->getAttribute('id');
|
|
}
|
|
$aUniquenessRules[$sCurrentRuleId]['attributes'] = $aUniquenessAttributes;
|
|
} else {
|
|
$aUniquenessRules[$sCurrentRuleId]['attributes'] = null;
|
|
}
|
|
|
|
$aUniquenessRules[$sCurrentRuleId]['filter'] = $oUniquenessSingleRule->GetChildText('filter');
|
|
$aUniquenessRules[$sCurrentRuleId]['disabled'] = $this->GetPropBooleanConverted($oUniquenessSingleRule, 'disabled', null);
|
|
$aUniquenessRules[$sCurrentRuleId]['is_blocking'] = $this->GetPropBooleanConverted($oUniquenessSingleRule, 'is_blocking',
|
|
null);
|
|
}
|
|
|
|
// we will check for rules validity later as for now we don't have objects hierarchy (see \MetaModel::InitClasses)
|
|
$aClassParams['uniqueness_rules'] = var_export($aUniquenessRules, true);
|
|
}
|
|
|
|
// Finalize class params declaration
|
|
//
|
|
$sClassParams = $this->GetAssociativeArrayAsPhpCode($aClassParams);
|
|
|
|
// Comment on top of the class declaration
|
|
//
|
|
$sCodeComment = $oProperties->GetChildText('comment');
|
|
|
|
// Fields
|
|
//
|
|
$oFields = $oClass->GetOptionalElement('fields');
|
|
if ($oFields)
|
|
{
|
|
$this->CompileFiles($oFields, $sTempTargetDir.'/'.$sModuleRelativeDir, $sFinalTargetDir.'/'.$sModuleRelativeDir, '');
|
|
}
|
|
$sAttributes = '';
|
|
$aTagFieldsInfo = array();
|
|
/** @var \DOMElement $oField */
|
|
foreach($this->oFactory->ListFields($oClass) as $oField)
|
|
{
|
|
try
|
|
{
|
|
// $oField
|
|
$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;
|
|
}
|
|
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);
|
|
$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);
|
|
}
|
|
|
|
$aParams = array();
|
|
foreach($aParameters as $sKey => $sValue)
|
|
{
|
|
if (!is_null($sValue))
|
|
{
|
|
$aParams[] = '"'.$sKey.'"=>'.$sValue;
|
|
}
|
|
}
|
|
$sParams = implode(', ', $aParams);
|
|
$sAttributes .= " MetaModel::Init_AddAttribute(new $sAttType(\"$sAttCode\", array($sParams)));\n";
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
throw new DOMFormatException("Field: '$sAttCode', (type: $sAttType), ".$e->getMessage());
|
|
}
|
|
}
|
|
|
|
// Lifecycle
|
|
//
|
|
$sLifecycle = '';
|
|
$sHighlightScale = '';
|
|
$oLifecycle = $oClass->GetOptionalElement('lifecycle');
|
|
if ($oLifecycle) {
|
|
$sLifecycle .= "\t\t// Lifecycle (status attribute: $sStateAttCode)\n";
|
|
$sLifecycle .= "\t\t//\n";
|
|
|
|
$oStimuli = $oLifecycle->GetUniqueElement('stimuli');
|
|
foreach ($oStimuli->getElementsByTagName('stimulus') as $oStimulus)
|
|
{
|
|
$sStimulus = $oStimulus->getAttribute('id');
|
|
$sStimulusClass = $oStimulus->getAttribute('xsi:type');
|
|
|
|
$sLifecycle .= " MetaModel::Init_DefineStimulus(new ".$sStimulusClass."(\"".$sStimulus."\", array()));\n";
|
|
}
|
|
$oHighlightScale = $oLifecycle->GetUniqueElement('highlight_scale', false);
|
|
if ($oHighlightScale)
|
|
{
|
|
$sHighlightScale = "\t\t// Higlight Scale\n";
|
|
$sHighlightScale .= " MetaModel::Init_DefineHighlightScale( array(\n";
|
|
|
|
$this->CompileFiles($oHighlightScale, $sTempTargetDir.'/'.$sModuleRelativeDir, $sFinalTargetDir.'/'.$sModuleRelativeDir, '');
|
|
|
|
foreach ($oHighlightScale->getElementsByTagName('item') as $oItem)
|
|
{
|
|
$sItemCode = $oItem->getAttribute('id');
|
|
$fRank = (float)$oItem->GetChildText('rank');
|
|
$sColor = $oItem->GetChildText('color');
|
|
if (($sIcon = $oItem->GetChildText('icon')) && (strlen($sIcon) > 0))
|
|
{
|
|
$sIcon = $sModuleRelativeDir.'/'.$sIcon;
|
|
$sIcon = "utils::GetAbsoluteUrlModulesRoot().'$sIcon'";
|
|
}
|
|
else
|
|
{
|
|
$sIcon = "''";
|
|
}
|
|
switch($sColor)
|
|
{
|
|
// Known PHP constants: keep the literal value as-is
|
|
case 'HILIGHT_CLASS_CRITICAL':
|
|
case 'HIGHLIGHT_CLASS_CRITICAL':
|
|
$sColor = 'HILIGHT_CLASS_CRITICAL';
|
|
break;
|
|
|
|
case 'HILIGHT_CLASS_OK':
|
|
case 'HIGHLIGHT_CLASS_OK':
|
|
$sColor = 'HILIGHT_CLASS_OK';
|
|
break;
|
|
|
|
case 'HIGHLIGHT_CLASS_WARNING':
|
|
case 'HILIGHT_CLASS_WARNING':
|
|
$sColor = 'HILIGHT_CLASS_WARNING';
|
|
break;
|
|
|
|
case 'HIGHLIGHT_CLASS_NONE':
|
|
case 'HILIGHT_CLASS_NONE':
|
|
$sColor = 'HILIGHT_CLASS_NONE';
|
|
break;
|
|
|
|
default:
|
|
// Future extension, specify your own color??
|
|
$sColor = "'".addslashes($sColor)."'";
|
|
}
|
|
$sHighlightScale .= " '$sItemCode' => array('rank' => $fRank, 'color' => $sColor, 'icon' => $sIcon),\n";
|
|
|
|
}
|
|
$sHighlightScale .= " ));\n";
|
|
}
|
|
|
|
$oStates = $oLifecycle->GetUniqueElement('states');
|
|
$aStatesDependencies = array();
|
|
$aStates = array();
|
|
foreach ($oStates->getElementsByTagName('state') as $oState)
|
|
{
|
|
$aStatesDependencies[$oState->getAttribute('id')] = $oState->GetChildText('inherit_flags_from', '');
|
|
$aStates[$oState->getAttribute('id')] = $oState;
|
|
}
|
|
$aStatesOrder = array();
|
|
while (count($aStatesOrder) < count($aStatesDependencies))
|
|
{
|
|
$iResolved = 0;
|
|
foreach($aStatesDependencies as $sState => $sInheritFrom)
|
|
{
|
|
if (is_null($sInheritFrom))
|
|
{
|
|
// Already recorded as resolved
|
|
continue;
|
|
}
|
|
elseif ($sInheritFrom == '')
|
|
{
|
|
// Resolved
|
|
$aStatesOrder[$sState] = $sInheritFrom;
|
|
$aStatesDependencies[$sState] = null;
|
|
$iResolved++;
|
|
}
|
|
elseif (isset($aStatesOrder[$sInheritFrom]))
|
|
{
|
|
// Resolved
|
|
$aStatesOrder[$sState] = $sInheritFrom;
|
|
$aStatesDependencies[$sState] = null;
|
|
$iResolved++;
|
|
}
|
|
}
|
|
if ($iResolved == 0)
|
|
{
|
|
// No change on this loop -> there are unmet dependencies
|
|
$aRemainingDeps = array();
|
|
foreach($aStatesDependencies as $sState => $sParentState)
|
|
{
|
|
if (strlen($sParentState) > 0)
|
|
{
|
|
$aRemainingDeps[] = $sState.' ('.$sParentState.')';
|
|
}
|
|
}
|
|
throw new DOMFormatException("Could not solve inheritance for states: ".implode(', ', $aRemainingDeps));
|
|
}
|
|
}
|
|
foreach ($aStatesOrder as $sState => $foo)
|
|
{
|
|
$oState = $aStates[$sState];
|
|
$oInitialStatePath = $oState->GetOptionalElement('initial_state_path');
|
|
if ($oInitialStatePath)
|
|
{
|
|
$aInitialStatePath = array();
|
|
foreach ($oInitialStatePath->getElementsByTagName('state_ref') as $oIntermediateState)
|
|
{
|
|
$aInitialStatePath[] = "'".$oIntermediateState->GetText()."'";
|
|
}
|
|
$sInitialStatePath = 'Array('.implode(', ', $aInitialStatePath).')';
|
|
}
|
|
|
|
$sLifecycle .= " MetaModel::Init_DefineState(\n";
|
|
$sLifecycle .= " \"".$sState."\",\n";
|
|
$sLifecycle .= " array(\n";
|
|
$sAttributeInherit = $oState->GetChildText('inherit_flags_from', '');
|
|
$sLifecycle .= " \"attribute_inherit\" => '$sAttributeInherit',\n";
|
|
$oHighlight = $oState->GetUniqueElement('highlight', false);
|
|
if ($oHighlight)
|
|
{
|
|
$sCode = $oHighlight->GetChildText('code', '');
|
|
if ($sCode != '')
|
|
{
|
|
$sLifecycle .= " 'highlight' => array('code' => '$sCode'),\n";
|
|
}
|
|
|
|
}
|
|
|
|
$sLifecycle .= " \"attribute_list\" => array(\n";
|
|
|
|
$oFlags = $oState->GetUniqueElement('flags');
|
|
foreach ($oFlags->getElementsByTagName('attribute') as $oAttributeNode)
|
|
{
|
|
$sFlags = $this->FlagsToPHP($oAttributeNode);
|
|
if (strlen($sFlags) > 0)
|
|
{
|
|
$sAttCode = $oAttributeNode->GetAttribute('id');
|
|
$sLifecycle .= " '$sAttCode' => $sFlags,\n";
|
|
}
|
|
}
|
|
|
|
$sLifecycle .= " ),\n";
|
|
if (!is_null($oInitialStatePath))
|
|
{
|
|
$sLifecycle .= " \"initial_state_path\" => $sInitialStatePath,\n";
|
|
}
|
|
$sLifecycle .= " )\n";
|
|
$sLifecycle .= " );\n";
|
|
|
|
$oTransitions = $oState->GetUniqueElement('transitions');
|
|
foreach ($oTransitions->getElementsByTagName('transition') as $oTransition)
|
|
{
|
|
$sStimulus = $oTransition->getAttribute('id');
|
|
$sTargetState = $oTransition->GetChildText('target');
|
|
|
|
$oActions = $oTransition->GetUniqueElement('actions');
|
|
$aVerbs = array();
|
|
foreach ($oActions->getElementsByTagName('action') as $oAction)
|
|
{
|
|
$sVerb = $oAction->GetChildText('verb');
|
|
$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).")";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Old (pre 2.1.0) format, when no parameter is specified, assume 1 parameter: reference sStimulusCode
|
|
$aActionParams[] = "array('type' => 'reference', 'value' => 'sStimulusCode')";
|
|
}
|
|
$sActionParams = 'array('.implode(', ', $aActionParams).')';
|
|
$aVerbs[] = "array('verb' => '$sVerb', 'params' => $sActionParams)";
|
|
}
|
|
$sActions = implode(', ', $aVerbs);
|
|
|
|
$sLifecycle .= " MetaModel::Init_DefineTransition(\"$sState\", \"$sStimulus\", array(\n";
|
|
$sLifecycle .= " \"target_state\"=>\"$sTargetState\",\n";
|
|
$sLifecycle .= " \"actions\"=>array($sActions),\n";
|
|
$sLifecycle .= " \"user_restriction\"=>null,\n";
|
|
$sLifecycle .= " \"attribute_list\"=>array(\n";
|
|
|
|
$oFlags = $oTransition->GetOptionalElement('flags');
|
|
if($oFlags !== null)
|
|
{
|
|
foreach ($oFlags->getElementsByTagName('attribute') as $oAttributeNode)
|
|
{
|
|
$sFlags = $this->FlagsToPHP($oAttributeNode);
|
|
if (strlen($sFlags) > 0)
|
|
{
|
|
$sAttCode = $oAttributeNode->GetAttribute('id');
|
|
$sLifecycle .= " '$sAttCode' => $sFlags,\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
$sLifecycle .= " )\n";
|
|
$sLifecycle .= " ));\n";
|
|
}
|
|
}
|
|
}
|
|
// No "real" lifecycle with stimuli and such but still a state attribute, we need to define states from the enum. values
|
|
elseif ($oFieldsSemantic && $oStateAttribute) {
|
|
$sLifecycle .= "\t\t// States but no lifecycle declared in XML (status attribute: $sStateAttCode)\n";
|
|
$sLifecycle .= "\t\t//\n";
|
|
|
|
// Note: We can't use ModelFactory::GetField() as the current clas doesn't seem to be loaded yet.
|
|
$oField = $this->oFactory->GetNodes('field[@id="'.$sStateAttCode.'"]', $oFields)->item(0);
|
|
if ($oField == null) {
|
|
throw new DOMFormatException("Non existing attribute '$sStateAttCode'", null, null, $oStateAttribute);
|
|
}
|
|
$oValues = $oField->GetUniqueElement('values');
|
|
$oValueNodes = $oValues->getElementsByTagName('value');
|
|
foreach ($oValueNodes as $oValue) {
|
|
$sLifecycle .= " MetaModel::Init_DefineState(\n";
|
|
$sLifecycle .= " \"".$oValue->GetText()."\",\n";
|
|
$sLifecycle .= " array(\n";
|
|
$sLifecycle .= " \"attribute_inherit\" => '',\n";
|
|
$sLifecycle .= " \"attribute_list\" => array()\n";
|
|
$sLifecycle .= " )\n";
|
|
$sLifecycle .= " );\n";
|
|
}
|
|
}
|
|
|
|
// ZLists
|
|
//
|
|
$aListRef = array(
|
|
'details' => 'details',
|
|
'standard_search' => 'search',
|
|
'default_search' => 'default_search',
|
|
'list' => 'list',
|
|
);
|
|
|
|
$oPresentation = $oClass->GetUniqueElement('presentation');
|
|
$sZlists = '';
|
|
foreach ($aListRef as $sListCode => $sListTag)
|
|
{
|
|
$oListNode = $oPresentation->GetOptionalElement($sListTag);
|
|
if ($oListNode)
|
|
{
|
|
$aAttributes = $oListNode->GetNodeAsArrayOfItems();
|
|
if(!is_array($aAttributes))
|
|
{
|
|
$aAttributes = array();
|
|
}
|
|
$this->ArrayOfItemsToZList($aAttributes);
|
|
|
|
$sZAttributes = var_export($aAttributes, true);
|
|
$sZlists .= " MetaModel::Init_SetZListItems('$sListCode', $sZAttributes);\n";
|
|
}
|
|
}
|
|
|
|
// Methods
|
|
$sMethods = "";
|
|
$oMethods = $oClass->GetUniqueElement('methods');
|
|
foreach($oMethods->getElementsByTagName('method') as $oMethod)
|
|
{
|
|
$sMethodCode = $oMethod->GetChildText('code');
|
|
if ($sMethodComment = $oMethod->GetChildText('comment', null))
|
|
{
|
|
$sMethods .= "\n\t$sMethodComment\n".$sMethodCode."\n";
|
|
}
|
|
else
|
|
{
|
|
$sMethods .= "\n\n".$sMethodCode."\n";
|
|
}
|
|
}
|
|
|
|
// Relations
|
|
//
|
|
$oRelations = $oClass->GetOptionalElement('relations');
|
|
if ($oRelations)
|
|
{
|
|
$aRelations = array();
|
|
foreach($oRelations->getElementsByTagName('relation') as $oRelation)
|
|
{
|
|
$sRelationId = $oRelation->getAttribute('id');
|
|
$this->aRelations[$sRelationId] = array('id' => $sRelationId);
|
|
|
|
$oNeighbours = $oRelation->GetUniqueElement('neighbours');
|
|
foreach($oNeighbours->getElementsByTagName('neighbour') as $oNeighbour)
|
|
{
|
|
$sNeighbourId = $oNeighbour->getAttribute('id');
|
|
|
|
$sDirection = $oNeighbour->GetChildText('direction', 'both');
|
|
$sAttribute = $oNeighbour->GetChildText('attribute');
|
|
$sQueryDown = $oNeighbour->GetChildText('query_down');
|
|
$sQueryUp = $oNeighbour->GetChildText('query_up');
|
|
|
|
if (($sQueryDown == '') && ($sAttribute == ''))
|
|
{
|
|
throw new DOMFormatException("Relation '$sRelationId/$sNeighbourId': either a query or an attribute must be specified");
|
|
}
|
|
if (($sQueryDown != '') && ($sAttribute != ''))
|
|
{
|
|
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 == ''))
|
|
{
|
|
throw new DOMFormatException("Relation '$sRelationId/$sNeighbourId': missing the query_up specification");
|
|
}
|
|
}
|
|
elseif ($sDirection == 'down')
|
|
{
|
|
// Ok
|
|
}
|
|
else
|
|
{
|
|
throw new DOMFormatException("Relation '$sRelationId/$sNeighbourId': unknown direction ($sDirection), expecting 'both' or 'down'");
|
|
}
|
|
$aRelations[$sRelationId][$sNeighbourId] = array(
|
|
'_legacy_' => false,
|
|
'sDirection' => $sDirection,
|
|
'sDefinedInClass' => $sClass,
|
|
'sNeighbour' => $sNeighbourId,
|
|
'sQueryDown' => $sQueryDown,
|
|
'sQueryUp' => $sQueryUp,
|
|
'sAttribute' => $sAttribute,
|
|
);
|
|
}
|
|
}
|
|
|
|
$sMethods .= "\tpublic static function GetRelationQueriesEx(\$sRelCode)\n";
|
|
$sMethods .= "\t{\n";
|
|
$sMethods .= "\t\tswitch (\$sRelCode)\n";
|
|
$sMethods .= "\t\t{\n";
|
|
foreach ($aRelations as $sRelationId => $aRelationData)
|
|
{
|
|
$sMethods .= "\t\tcase '$sRelationId':\n";
|
|
$sMethods .= "\t\t\t\$aRels = array(\n";
|
|
foreach ($aRelationData as $sNeighbourId => $aData)
|
|
{
|
|
//$sData = str_replace("\n", "\n\t\t\t\t", var_export($aData, true));
|
|
$sData = var_export($aData, true);
|
|
$sMethods .= "\t\t\t\t'$sNeighbourId' => $sData,\n";
|
|
}
|
|
$sMethods .= "\t\t\t);\n";
|
|
$sMethods .= "\t\t\treturn array_merge(\$aRels, parent::GetRelationQueriesEx(\$sRelCode));\n\n";
|
|
}
|
|
$sMethods .= "\t\tdefault:\n";
|
|
$sMethods .= "\t\t\treturn parent::GetRelationQueriesEx(\$sRelCode);\n";
|
|
$sMethods .= "\t\t}\n";
|
|
$sMethods .= "\t}\n";
|
|
}
|
|
|
|
// Let's make the whole class declaration
|
|
//
|
|
$sClassName = $oClass->getAttribute('id');
|
|
$bIsAbstractClass = ($oProperties->GetChildText('abstract') == 'true');
|
|
$oPhpParent = $oClass->GetUniqueElement('php_parent', false);
|
|
$aRequiredFiles = [];
|
|
if ($oPhpParent)
|
|
{
|
|
$sParentClass = $oPhpParent->GetChildText('name', '');
|
|
if ($sParentClass == '')
|
|
{
|
|
throw new Exception("Failed to process class '".$oClass->getAttribute('id')."', missing required tag 'name' under 'php_parent'.");
|
|
}
|
|
$sIncludeFile = $oPhpParent->GetChildText('file', '');
|
|
if ($sIncludeFile != '')
|
|
{
|
|
$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
|
|
{
|
|
$sParentClass = $oClass->GetChildText('parent', 'DBObject');
|
|
}
|
|
$sInitMethodCalls =
|
|
<<<EOF
|
|
$sAttributes
|
|
$sLifecycle
|
|
$sHighlightScale
|
|
$sZlists;
|
|
EOF;
|
|
// some other stuff (magical attributes like friendlyName) are done in MetaModel::InitClasses and though not present in the
|
|
// generated PHP
|
|
$sPHP .= $this->GeneratePhpCodeForClass($sClassName, $sParentClass, $sClassParams, $sInitMethodCalls, $bIsAbstractClass, $sMethods, $aRequiredFiles, $sCodeComment);
|
|
|
|
// N°931 generates TagFieldData classes for AttributeTag fields
|
|
if (!empty($aTagFieldsInfo))
|
|
{
|
|
$sTagClassParentClass = "TagSetFieldData";
|
|
$aTagClassParams = array
|
|
(
|
|
'category' => 'bizmodel',
|
|
'key_type' => 'autoincrement',
|
|
'name_attcode' => array('label'),
|
|
'state_attcode' => '',
|
|
'reconc_keys' => array('code'),
|
|
'db_table' => '', // no need to have a corresponding table : this class exists only for rights, no additional field
|
|
'db_key_field' => 'id',
|
|
'db_finalclass_field' => 'finalclass',
|
|
);
|
|
$sTagInitMethodCalls =
|
|
<<<EOF
|
|
MetaModel::Init_SetZListItems('default_search', array (
|
|
0 => 'code',
|
|
1 => 'label',
|
|
));
|
|
EOF
|
|
;
|
|
foreach ($aTagFieldsInfo as $sTagFieldName)
|
|
{
|
|
$sTagClassName = static::GetTagDataClassName($sClassName, $sTagFieldName);
|
|
$sTagClassParams = var_export($aTagClassParams, true);
|
|
$sPHP .= $this->GeneratePhpCodeForClass($sTagClassName, $sTagClassParentClass, $sTagClassParams, $sTagInitMethodCalls);
|
|
}
|
|
}
|
|
|
|
if (strlen($sCss) > 0) {
|
|
if (array_key_exists($sClass, $this->aClassesCSSRules) === false) {
|
|
$this->aClassesCSSRules[$sClass] = '';
|
|
}
|
|
$this->aClassesCSSRules[$sClass] .= $sCss;
|
|
}
|
|
|
|
return $sPHP;
|
|
}
|
|
|
|
/**
|
|
* @internal This method is public in order to be used in the tests
|
|
*
|
|
* @param \MFElement $oNode Style node, can be either a <style> node of a specific field value, or a <default_style> node of a field
|
|
* @param string $sElementType Type of element the style is for: 'enum' for an Attribute(Meta)Enum field, 'class' for a class. {@see self::ENUM_STYLE_HOST_ELEMENT_TYPE_CLASS, ...}
|
|
* @param string $sClass
|
|
* @param string|null $sAttCode Optional, att. code
|
|
* @param string|null $sValue Optional, value (code) of the field. Used onlyIf null, then it will generate data as for the default style
|
|
*
|
|
* @return array Data generated from a style node of the DM ['orm_style_instantiation' => <GENERATED_ORMSTYLE_INSTANTIATION>, 'scss' => <GENERATED_SCSS>]
|
|
* @throws \DOMFormatException
|
|
*/
|
|
public function GenerateStyleDataFromNode(MFElement $oNode, string $sModuleRelDir, string $sElementType, string $sClass, ?string $sAttCode = null, ?string $sValue = null): array
|
|
{
|
|
$aData = [];
|
|
|
|
/** @var $sCssClassSuffix Optional suffix for the CSS classes depending on the given parameters */
|
|
$sCssClassSuffix = "";
|
|
/** @var $sOrmStylePrefix Prefix used for the ormStyle instantiation in PHP */
|
|
$sOrmStylePrefix = "";
|
|
|
|
// In case $sAttCode and optionally $sValue are passed, we prepare additional info. Typically used for (meta)enum attributes
|
|
if (is_null($sAttCode) === false) {
|
|
$sCssClassSuffix .= "-$sAttCode";
|
|
|
|
if (is_null($sValue) === false) {
|
|
$sCssClassSuffix .= "-".utils::GetSafeId($sValue);
|
|
$sOrmStylePrefix = "'$sValue' => ";
|
|
}
|
|
}
|
|
|
|
// Retrieve colors (mandatory/optional depending on the element type)
|
|
// Note: For now we can't use CSS variables (only SCSS variables) in the style XML definition as the ibo-adjust-alpha() / ibo-adjust-lightness() used a few steps below do not support them,
|
|
// if this ever should be considered, the following article might help: https://codyhouse.co/blog/post/how-to-combine-sass-color-functions-and-css-variables#other-color-functions
|
|
if ($sElementType === self::ENUM_STYLE_HOST_ELEMENT_TYPE_CLASS) {
|
|
$sMainColorForCss = $this->GetPropString($oNode, 'main_color', null, false);
|
|
$sMainColorForOrm = $this->GetPropString($oNode, 'main_color', null);
|
|
if (is_null($sMainColorForOrm)) {
|
|
// TODO: Check for main color in parent classes definition is currently done in MetaModel::GetClassStyle() at runtime but it should be done here at compile time
|
|
$sMainColorForOrm = "null";
|
|
}
|
|
|
|
$sComplementaryColorForCss = $this->GetPropString($oNode, 'complementary_color', null, false);
|
|
$sComplementaryColorForOrm = $this->GetPropString($oNode, 'complementary_color', null);
|
|
if (is_null($sComplementaryColorForOrm)) {
|
|
// TODO: Check for main color in parent classes definition is currently done in MetaModel::GetClassStyle() at runtime but it should be done here at compile time
|
|
$sComplementaryColorForOrm = "null";
|
|
}
|
|
} else {
|
|
$sMainColorForCss = $this->GetMandatoryPropString($oNode, 'main_color', false);
|
|
$sMainColorForOrm = $this->GetMandatoryPropString($oNode, 'main_color');
|
|
|
|
$sComplementaryColorForCss = $this->GetMandatoryPropString($oNode, 'complementary_color', false);
|
|
$sComplementaryColorForOrm = $this->GetMandatoryPropString($oNode, 'complementary_color');
|
|
}
|
|
$bHasMainColor = is_null($sMainColorForCss) === false;
|
|
$bHasComplementaryColor = is_null($sComplementaryColorForCss) === false;
|
|
$bHasAtLeastOneColor = $bHasMainColor || $bHasComplementaryColor;
|
|
|
|
// Optional decoration classes
|
|
$sDecorationClasses = $this->GetPropString($oNode, 'decoration_classes', null);
|
|
if (is_null($sDecorationClasses)) {
|
|
$sDecorationClasses = "null";
|
|
}
|
|
|
|
// Optional icon
|
|
$sIconRelPath = $this->GetPropString($oNode, 'icon', null, false);
|
|
if (is_null($sIconRelPath)) {
|
|
$sIconRelPath = "null";
|
|
} else {
|
|
$sIconRelPath = "'$sModuleRelDir/$sIconRelPath'";
|
|
}
|
|
|
|
// CSS classes representing the element (regular and alternative)
|
|
$sCssRegularClass = "ibo-dm-$sElementType--$sClass$sCssClassSuffix";
|
|
$sCssRegularClassForOrm = $bHasAtLeastOneColor ? "'$sCssRegularClass'" : "null";
|
|
|
|
$sCssAlternativeClass = "ibo-dm-$sElementType-alt--$sClass$sCssClassSuffix";
|
|
$sCssAlternativeClassForOrm = $bHasAtLeastOneColor ? "'$sCssAlternativeClass'" : "null";
|
|
|
|
// Generate SCSS declaration
|
|
$sScss = "";
|
|
if ($bHasAtLeastOneColor) {
|
|
if ($bHasMainColor) {
|
|
$sMainColorScssVariableName = "\$$sCssRegularClass--main-color";
|
|
$sMainColorCssVariableName = "--$sCssRegularClass--main-color";
|
|
|
|
$sMainColorScssVariableDeclaration = "$sMainColorScssVariableName: $sMainColorForCss !default;";
|
|
$sMainColorCssVariableDeclaration = "$sMainColorCssVariableName: #{{$sMainColorScssVariableName}};";
|
|
|
|
$sCssRegularClassMainColorDeclaration = "--ibo-main-color: $sMainColorScssVariableName;";
|
|
// Note: We have to manually force the alpha channel in case the given color is transparent
|
|
$sCssRegularClassMainColor100Declaration = "--ibo-main-color--100: ibo-adjust-alpha(ibo-adjust-lightness($sMainColorScssVariableName, \$ibo-color-base-lightness-100), \$ibo-color-base-opacity-for-lightness-100);";
|
|
$sCssRegularClassMainColor900Declaration = "--ibo-main-color--900: ibo-adjust-alpha(ibo-adjust-lightness($sMainColorScssVariableName, \$ibo-color-base-lightness-900), \$ibo-color-base-opacity-for-lightness-900);";
|
|
|
|
$sCssAlternativeClassComplementaryColorDeclaration = "--ibo-complementary-color: $sMainColorScssVariableName;";
|
|
} else {
|
|
$sMainColorScssVariableDeclaration = null;
|
|
|
|
$sCssRegularClassMainColorDeclaration = null;
|
|
$sCssRegularClassMainColor900Declaration = null;
|
|
|
|
$sCssAlternativeClassComplementaryColorDeclaration = null;
|
|
}
|
|
|
|
if ($bHasComplementaryColor) {
|
|
$sComplementaryColorScssVariableName = "\$$sCssRegularClass--complementary-color";
|
|
$sComplementaryColorCssVariableName = "--$sCssRegularClass--complementary-color";
|
|
|
|
$sComplementaryScssVariableDeclaration = "$sComplementaryColorScssVariableName: $sComplementaryColorForCss !default;";
|
|
$sComplementaryCssVariableDeclaration = "$sComplementaryColorCssVariableName: #{{$sComplementaryColorScssVariableName}};";
|
|
|
|
$sCssRegularClassComplementaryColorDeclaration = "--ibo-complementary-color: $sComplementaryColorScssVariableName;";
|
|
|
|
$sCssAlternativeClassMainColorDeclaration = "--ibo-main-color: $sComplementaryColorScssVariableName;";
|
|
} else {
|
|
$sComplementaryScssVariableDeclaration = null;
|
|
$sComplementaryCssVariableDeclaration = null;
|
|
|
|
$sCssRegularClassComplementaryColorDeclaration = null;
|
|
|
|
$sCssAlternativeClassMainColorDeclaration = null;
|
|
}
|
|
|
|
$sScss .= <<<CSS
|
|
$sMainColorScssVariableDeclaration
|
|
$sComplementaryScssVariableDeclaration
|
|
|
|
:root {
|
|
$sMainColorCssVariableDeclaration
|
|
$sComplementaryCssVariableDeclaration
|
|
}
|
|
|
|
.$sCssRegularClass {
|
|
$sCssRegularClassMainColorDeclaration
|
|
$sCssRegularClassMainColor100Declaration
|
|
$sCssRegularClassMainColor900Declaration
|
|
$sCssRegularClassComplementaryColorDeclaration
|
|
}
|
|
.$sCssAlternativeClass {
|
|
$sCssAlternativeClassMainColorDeclaration
|
|
$sCssAlternativeClassComplementaryColorDeclaration
|
|
}
|
|
|
|
CSS;
|
|
}
|
|
$aData['scss'] = $sScss;
|
|
|
|
// Generate ormStyle instantiation
|
|
// - Convert SCSS variable to CSS variable use as SCSS variable cannot be used elsewhere than during SCSS compiling
|
|
// Note: We check the $sXXXColorForCSS instead of the $sXXXColorForOrm because its value has been altered.
|
|
if ($bHasMainColor && (stripos($sMainColorForCss, '$') === 0)) {
|
|
$sMainColorForOrm = "'var($sMainColorCssVariableName)'";
|
|
}
|
|
if ($bHasComplementaryColor && (stripos($sComplementaryColorForCss, '$') === 0)) {
|
|
$sComplementaryColorForOrm = "'var($sComplementaryColorCssVariableName)'";
|
|
}
|
|
$aData['orm_style_instantiation'] = "$sOrmStylePrefix new ormStyle($sCssRegularClassForOrm, $sCssAlternativeClassForOrm, $sMainColorForOrm, $sComplementaryColorForOrm, $sDecorationClasses, $sIconRelPath)";
|
|
|
|
return $aData;
|
|
}
|
|
|
|
private static function GetTagDataClassName($sClass, $sAttCode)
|
|
{
|
|
$sTagSuffix = $sClass.'__'.$sAttCode;
|
|
|
|
return 'TagSetFieldDataFor_'.$sTagSuffix;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param DesignElement $oMenu
|
|
* @param string $sTempTargetDir
|
|
* @param string $sFinalTargetDir
|
|
* @param string $sModuleRelativeDir
|
|
* @param \iTopWebPage $oP
|
|
*
|
|
* @return array
|
|
* @throws \DOMFormatException
|
|
*/
|
|
protected function CompileMenu($oMenu, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir, $oP)
|
|
{
|
|
$this->CompileFiles($oMenu, $sTempTargetDir.'/'.$sModuleRelativeDir, $sFinalTargetDir.'/'.$sModuleRelativeDir, $sModuleRelativeDir);
|
|
|
|
$sMenuId = $oMenu->getAttribute("id");
|
|
$sMenuClass = $oMenu->getAttribute("xsi:type");
|
|
|
|
$sParent = $oMenu->GetChildText('parent', null);
|
|
if ($sParent)
|
|
{
|
|
$sParentSpec = "\$__comp_menus__['$sParent']->GetIndex()";
|
|
}
|
|
else
|
|
{
|
|
$sParentSpec = '-1';
|
|
}
|
|
|
|
$fRank = (float) $oMenu->GetChildText('rank');
|
|
if ($sEnableClass = $oMenu->GetChildText('enable_class'))
|
|
{
|
|
$sEnableAction = $oMenu->GetChildText('enable_action', 'UR_ACTION_MODIFY');
|
|
$sEnablePermission = $oMenu->GetChildText('enable_permission', 'UR_ALLOWED_YES');
|
|
$sEnableStimulus = $oMenu->GetChildText('enable_stimulus');
|
|
if ($sEnableStimulus != null)
|
|
{
|
|
$sOptionalEnableParams = ", '$sEnableClass', $sEnableAction, $sEnablePermission, '$sEnableStimulus'";
|
|
}
|
|
else
|
|
{
|
|
$sOptionalEnableParams = ", '$sEnableClass', $sEnableAction, $sEnablePermission, null";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$sOptionalEnableParams = ", null, UR_ACTION_MODIFY, UR_ALLOWED_YES, null";
|
|
}
|
|
|
|
switch($sMenuClass)
|
|
{
|
|
case 'WebPageMenuNode':
|
|
$sUrl = $oMenu->GetChildText('url');
|
|
$sUrlSpec = $this->PathToPHP($sUrl, $sModuleRelativeDir, true /* Url */);
|
|
$bIsLinkInNewWindow = $this->GetPropBooleanConverted($oMenu, 'in_new_window', false);
|
|
if ($bIsLinkInNewWindow)
|
|
{
|
|
$sOptionalEnableParams .= ', true';
|
|
}
|
|
$sNewMenu = "new WebPageMenuNode('$sMenuId', $sUrlSpec, $sParentSpec, $fRank {$sOptionalEnableParams});";
|
|
break;
|
|
|
|
case 'DashboardMenuNode':
|
|
$sTemplateFile = $oMenu->GetChildText('definition_file', '');
|
|
if ($sTemplateFile != '')
|
|
{
|
|
$sTemplateSpec = $this->PathToPHP($sTemplateFile, $sModuleRelativeDir);
|
|
}
|
|
else
|
|
{
|
|
$oDashboardDefinition = $oMenu->GetOptionalElement('definition');
|
|
if ($oDashboardDefinition == null)
|
|
{
|
|
throw(new DOMFormatException('Missing definition for Dashboard menu "'.$sMenuId.'" expecting either a tag "definition_file" or "definition".'));
|
|
}
|
|
$sFileName = strtolower(str_replace(array(':', '/', '\\', '*'), '_', $sMenuId)).'_dashboard.xml';
|
|
$sTemplateSpec = $this->PathToPHP($sFileName, $sModuleRelativeDir);
|
|
|
|
$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);
|
|
}
|
|
$oXMLDoc->save($sTempTargetDir.'/'.$sModuleRelativeDir.'/'.$sFileName);
|
|
}
|
|
$sNewMenu = "new DashboardMenuNode('$sMenuId', $sTemplateSpec, $sParentSpec, $fRank {$sOptionalEnableParams});";
|
|
break;
|
|
|
|
case 'ShortcutContainerMenuNode':
|
|
$sNewMenu = "new ShortcutContainerMenuNode('$sMenuId', $sParentSpec, $fRank {$sOptionalEnableParams});";
|
|
break;
|
|
|
|
case 'OQLMenuNode':
|
|
$sOQL = self::QuoteForPHP($oMenu->GetChildText('oql'));
|
|
$bSearch = ($oMenu->GetChildText('do_search') == '1') ? 'true' : 'false';
|
|
$sSearchFormOpenXML = $oMenu->GetChildText('search_form_open');
|
|
switch($sSearchFormOpenXML)
|
|
{
|
|
case '1':
|
|
$sSearchFormOpen = 'true';
|
|
break;
|
|
|
|
case '0':
|
|
$sSearchFormOpen = 'false';
|
|
break;
|
|
|
|
default:
|
|
$sSearchFormOpen = 'true';
|
|
}
|
|
$sNewMenu = "new OQLMenuNode('$sMenuId', $sOQL, $sParentSpec, $fRank, $bSearch {$sOptionalEnableParams}, $sSearchFormOpen);";
|
|
break;
|
|
|
|
case 'NewObjectMenuNode':
|
|
$sClass = $oMenu->GetChildText('class');
|
|
$sNewMenu = "new NewObjectMenuNode('$sMenuId', '$sClass', $sParentSpec, $fRank {$sOptionalEnableParams});";
|
|
break;
|
|
|
|
case 'SearchMenuNode':
|
|
$sClass = $oMenu->GetChildText('class');
|
|
$sNewMenu = "new SearchMenuNode('$sMenuId', '$sClass', $sParentSpec, $fRank, null {$sOptionalEnableParams});";
|
|
break;
|
|
|
|
case 'TemplateMenuNode':
|
|
$sTemplateFile = $oMenu->GetChildText('template_file');
|
|
$sTemplateSpec = $this->PathToPHP($sTemplateFile, $sModuleRelativeDir);
|
|
$sNewMenu = "new TemplateMenuNode('$sMenuId', $sTemplateSpec, $sParentSpec, $fRank {$sOptionalEnableParams});";
|
|
break;
|
|
|
|
case 'MenuGroup':
|
|
$oStyleNode = $oMenu->GetOptionalElement('style');
|
|
// Note: We use '' as the default value to ease the MenuGroup::__construct() call as we would have to make a different processing to not put the quotes around the parameter in case of null.
|
|
$sDecorationClasses = ($oStyleNode === null) ? '' : $oStyleNode->GetChildText('decoration_classes', '');
|
|
|
|
$sNewMenu = "new MenuGroup('$sMenuId', $fRank, '$sDecorationClasses' {$sOptionalEnableParams});";
|
|
break;
|
|
|
|
default:
|
|
$sNewMenu = "new $sMenuClass('$sMenuId', $fRank {$sOptionalEnableParams});";
|
|
}
|
|
|
|
$aPHPMenu = array("\$__comp_menus__['$sMenuId'] = $sNewMenu");
|
|
if ($sAutoReload = $oMenu->GetChildText('auto_reload'))
|
|
{
|
|
$sAutoReload = self::QuoteForPHP($sAutoReload);
|
|
$aPHPMenu[] = "\$__comp_menus__['$sMenuId']->SetParameters(array('auto_reload' => $sAutoReload));";
|
|
}
|
|
return $aPHPMenu;
|
|
}
|
|
|
|
/**
|
|
* Helper to compute the grant, taking any existing grant into account
|
|
*/
|
|
protected function CumulateGrant(&$aGrants, $sKey, $bGrant)
|
|
{
|
|
if (isset($aGrants[$sKey]))
|
|
{
|
|
if (!$bGrant)
|
|
{
|
|
$aGrants[$sKey] = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$aGrants[$sKey] = $bGrant;
|
|
}
|
|
}
|
|
|
|
protected function CompileUserRights($oUserRightsNode)
|
|
{
|
|
static $aActionsInShort = array(
|
|
'read' => 'r',
|
|
'bulk read' => 'br',
|
|
'write' => 'w',
|
|
'bulk write' => 'bw',
|
|
'delete' => 'd',
|
|
'bulk delete' => 'bd',
|
|
);
|
|
|
|
// Preliminary : create an index so that links will be taken into account implicitely
|
|
$aLinkToClasses = array();
|
|
$oClasses = $this->oFactory->ListAllClasses();
|
|
foreach($oClasses as $oClass)
|
|
{
|
|
$bIsLink = false;
|
|
$oProperties = $oClass->GetOptionalElement('properties');
|
|
if ($oProperties)
|
|
{
|
|
$bIsLink = (bool) $this->GetPropNumber($oProperties, 'is_link', 0);
|
|
}
|
|
if ($bIsLink)
|
|
{
|
|
foreach($this->oFactory->ListFields($oClass) as $oField)
|
|
{
|
|
$sAttType = $oField->getAttribute('xsi:type');
|
|
|
|
if (($sAttType == 'AttributeExternalKey') || ($sAttType == 'AttributeHierarchicalKey'))
|
|
{
|
|
$sOnTargetDel = $oField->GetChildText('on_target_delete');
|
|
if (($sOnTargetDel == 'DEL_AUTO') || ($sOnTargetDel == 'DEL_SILENT'))
|
|
{
|
|
$sTargetClass = $oField->GetChildText('target_class');
|
|
$aLinkToClasses[$oClass->getAttribute('id')][] = $sTargetClass;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Groups
|
|
//
|
|
$aGroupClasses = array();
|
|
$oGroups = $oUserRightsNode->GetUniqueElement('groups');
|
|
foreach($oGroups->getElementsByTagName('group') as $oGroup)
|
|
{
|
|
$sGroupId = $oGroup->getAttribute("id");
|
|
|
|
$aClasses = array();
|
|
$oClasses = $oGroup->GetUniqueElement('classes');
|
|
foreach($oClasses->getElementsByTagName('class') as $oClass)
|
|
{
|
|
|
|
$sClass = $oClass->getAttribute("id");
|
|
$aClasses[] = $sClass;
|
|
|
|
//$bSubclasses = $this->GetPropBoolean($oClass, 'subclasses', true);
|
|
//if ($bSubclasses)...
|
|
}
|
|
|
|
$aGroupClasses[$sGroupId] = $aClasses;
|
|
}
|
|
|
|
// Profiles and grants
|
|
//
|
|
$aProfiles = array();
|
|
// Hardcode the administrator profile
|
|
$aProfiles[1] = array(
|
|
'name' => 'Administrator',
|
|
'description' => 'Has the rights on everything (bypassing any control)',
|
|
);
|
|
|
|
$aGrants = array();
|
|
$oProfiles = $oUserRightsNode->GetUniqueElement('profiles');
|
|
foreach($oProfiles->getElementsByTagName('profile') as $oProfile)
|
|
{
|
|
$iProfile = $oProfile->getAttribute("id");
|
|
$sName = $oProfile->GetChildText('name');
|
|
$sDescription = $oProfile->GetChildText('description');
|
|
|
|
$oGroups = $oProfile->GetUniqueElement('groups');
|
|
foreach($oGroups->getElementsByTagName('group') as $oGroup)
|
|
{
|
|
$sGroupId = $oGroup->getAttribute("id");
|
|
|
|
$oActions = $oGroup->GetUniqueElement('actions');
|
|
foreach($oActions->getElementsByTagName('action') as $oAction)
|
|
{
|
|
$sAction = $oAction->getAttribute("id");
|
|
if (strpos($sAction, 'action:') === 0)
|
|
{
|
|
$sType = 'action';
|
|
$sActionCode = substr($sAction, strlen('action:'));
|
|
$sActionCode = $aActionsInShort[$sActionCode];
|
|
}
|
|
else
|
|
{
|
|
$sType = 'stimulus';
|
|
$sActionCode = substr($sAction, strlen('stimulus:'));
|
|
}
|
|
$sGrant = $oAction->GetText();
|
|
$bGrant = ($sGrant == 'allow');
|
|
|
|
if ($sGroupId == '*')
|
|
{
|
|
$aGrantClasses = array('*');
|
|
}
|
|
else
|
|
{
|
|
if (array_key_exists($sGroupId, $aGroupClasses) === false) {
|
|
SetupLog::Error("Profile \"$sName\" relies on group \"$sGroupId\" but it does not seem to be present in the DM yet (did you forgot a dependency in your module?)");
|
|
}
|
|
|
|
$aGrantClasses = $aGroupClasses[$sGroupId];
|
|
}
|
|
foreach ($aGrantClasses as $sClass)
|
|
{
|
|
if ($sType == 'stimulus')
|
|
{
|
|
$this->CumulateGrant($aGrants, $iProfile.'_'.$sClass.'_s_'.$sActionCode, $bGrant);
|
|
$this->CumulateGrant($aGrants, $iProfile.'_'.$sClass.'+_s_'.$sActionCode, $bGrant); // subclasses inherit this grant
|
|
}
|
|
else
|
|
{
|
|
$this->CumulateGrant($aGrants, $iProfile.'_'.$sClass.'_'.$sActionCode, $bGrant);
|
|
$this->CumulateGrant($aGrants, $iProfile.'_'.$sClass.'+_'.$sActionCode, $bGrant); // subclasses inherit this grant
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$aProfiles[$iProfile] = array(
|
|
'name' => $sName,
|
|
'description' => $sDescription,
|
|
);
|
|
}
|
|
|
|
$sProfiles = var_export($aProfiles, true);
|
|
$sGrants = var_export($aGrants, true);
|
|
$sLinkToClasses = var_export($aLinkToClasses, true);
|
|
|
|
$sPHP =
|
|
<<<EOF
|
|
//
|
|
// List of constant profiles
|
|
// - used by the class URP_Profiles at setup (create/update/delete records)
|
|
// - used by the addon UserRightsProfile to determine user rights
|
|
//
|
|
class ProfilesConfig
|
|
{
|
|
protected static \$aPROFILES = $sProfiles;
|
|
|
|
protected static \$aGRANTS = $sGrants;
|
|
|
|
protected static \$aLINKTOCLASSES = $sLinkToClasses;
|
|
|
|
// Now replaced by MetaModel::GetLinkClasses (working with 1.x)
|
|
// This function could be deprecated
|
|
public static function GetLinkClasses()
|
|
{
|
|
return self::\$aLINKTOCLASSES;
|
|
}
|
|
|
|
public static function GetProfileActionGrant(\$iProfileId, \$sClass, \$sAction)
|
|
{
|
|
\$bLegacyBehavior = MetaModel::GetConfig()->Get('user_rights_legacy');
|
|
|
|
// Search for a grant, stoping if any deny is encountered (allowance implies the verification of all paths)
|
|
\$bAllow = null;
|
|
|
|
// 1 - The class itself
|
|
//
|
|
\$sGrantKey = \$iProfileId.'_'.\$sClass.'_'.\$sAction;
|
|
if (isset(self::\$aGRANTS[\$sGrantKey]))
|
|
{
|
|
\$bAllow = self::\$aGRANTS[\$sGrantKey];
|
|
if (\$bLegacyBehavior) return \$bAllow;
|
|
if (!\$bAllow) return false;
|
|
}
|
|
|
|
// 2 - The parent classes, up to the root class
|
|
//
|
|
foreach (MetaModel::EnumParentClasses(\$sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false /*bRootFirst*/) as \$sParent)
|
|
{
|
|
\$sGrantKey = \$iProfileId.'_'.\$sParent.'+_'.\$sAction;
|
|
if (isset(self::\$aGRANTS[\$sGrantKey]))
|
|
{
|
|
\$bAllow = self::\$aGRANTS[\$sGrantKey];
|
|
if (\$bLegacyBehavior) return \$bAllow;
|
|
if (!\$bAllow) return false;
|
|
}
|
|
}
|
|
|
|
// 3 - The related classes (if the current is an N-N link with DEL_AUTO/DEL_SILENT)
|
|
//
|
|
\$bGrant = self::GetLinkActionGrant(\$iProfileId, \$sClass, \$sAction);
|
|
if (!is_null(\$bGrant))
|
|
{
|
|
\$bAllow = \$bGrant;
|
|
if (\$bLegacyBehavior) return \$bAllow;
|
|
if (!\$bAllow) return false;
|
|
}
|
|
|
|
// 4 - All (only for bizmodel)
|
|
// As the profiles now manage also grant_by_profile category,
|
|
// '*' is restricted to bizmodel to avoid openning the access for the existing profiles.
|
|
//
|
|
if (MetaModel::HasCategory(\$sClass, 'bizmodel'))
|
|
{
|
|
\$sGrantKey = \$iProfileId.'_*_'.\$sAction;
|
|
if (isset(self::\$aGRANTS[\$sGrantKey]))
|
|
{
|
|
\$bAllow = self::\$aGRANTS[\$sGrantKey];
|
|
if (\$bLegacyBehavior) return \$bAllow;
|
|
if (!\$bAllow) return false;
|
|
}
|
|
}
|
|
|
|
// null or true
|
|
return \$bAllow;
|
|
}
|
|
|
|
public static function GetProfileStimulusGrant(\$iProfileId, \$sClass, \$sStimulus)
|
|
{
|
|
\$sGrantKey = \$iProfileId.'_'.\$sClass.'_s_'.\$sStimulus;
|
|
if (isset(self::\$aGRANTS[\$sGrantKey]))
|
|
{
|
|
return self::\$aGRANTS[\$sGrantKey];
|
|
}
|
|
\$sGrantKey = \$iProfileId.'_*_s_'.\$sStimulus;
|
|
if (isset(self::\$aGRANTS[\$sGrantKey]))
|
|
{
|
|
return self::\$aGRANTS[\$sGrantKey];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// returns an array of id => array of column => php value(so-called "real value")
|
|
public static function GetProfilesValues()
|
|
{
|
|
return self::\$aPROFILES;
|
|
}
|
|
|
|
// Propagate the rights on classes onto the links themselves (the external keys must have DEL_AUTO or DEL_SILENT
|
|
//
|
|
protected static function GetLinkActionGrant(\$iProfileId, \$sClass, \$sAction)
|
|
{
|
|
if (array_key_exists(\$sClass, self::\$aLINKTOCLASSES))
|
|
{
|
|
// Get the grant for the remote classes. The resulting grant is:
|
|
// - One YES => YES
|
|
// - 100% undefined => undefined
|
|
// - otherwise => NO
|
|
//
|
|
|
|
// Having write allowed on the remote class implies write + delete on the N-N link class
|
|
if (\$sAction == 'd')
|
|
{
|
|
\$sRemoteAction = 'w';
|
|
}
|
|
elseif (\$sAction == 'bd')
|
|
{
|
|
\$sRemoteAction = 'bw';
|
|
}
|
|
else
|
|
{
|
|
\$sRemoteAction = \$sAction;
|
|
}
|
|
|
|
foreach (self::\$aLINKTOCLASSES[\$sClass] as \$sRemoteClass)
|
|
{
|
|
\$bUndefined = true;
|
|
\$bGrant = self::GetProfileActionGrant(\$iProfileId, \$sRemoteClass, \$sAction);
|
|
if (\$bGrant === true)
|
|
{
|
|
return true;
|
|
}
|
|
if (\$bGrant === false)
|
|
{
|
|
\$bUndefined = false;
|
|
}
|
|
}
|
|
if (!\$bUndefined)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
EOF;
|
|
return $sPHP;
|
|
} // function CompileUserRights
|
|
|
|
protected function CompileDictionaries($oDictionaries, $sTempTargetDir, $sFinalTargetDir)
|
|
{
|
|
$aLanguages = array();
|
|
foreach($oDictionaries as $oDictionaryNode)
|
|
{
|
|
$sLang = $oDictionaryNode->getAttribute('id');
|
|
$sEnglishLanguageDesc = $oDictionaryNode->GetChildText('english_description');
|
|
$sLocalizedLanguageDesc = $oDictionaryNode->GetChildText('localized_description');
|
|
$aLanguages[$sLang] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
|
|
|
|
$aEntriesPHP = array();
|
|
$oEntries = $oDictionaryNode->GetUniqueElement('entries');
|
|
foreach ($oEntries->getElementsByTagName('entry') as $oEntry)
|
|
{
|
|
$sStringCode = $oEntry->getAttribute('id');
|
|
$sValue = $oEntry->GetText();
|
|
$aEntriesPHP[] = "\t'$sStringCode' => ".self::QuoteForPHP(self::FilterDictString($sValue), true).",";
|
|
}
|
|
$sEntriesPHP = implode("\n", $aEntriesPHP);
|
|
|
|
$sPHPDict =
|
|
<<<EOF
|
|
<?php
|
|
//
|
|
// Dictionary built by the compiler for the language "$sLang"
|
|
//
|
|
Dict::SetEntries('$sLang', array(
|
|
$sEntriesPHP
|
|
));
|
|
EOF;
|
|
$sSafeLang = str_replace(' ', '-', strtolower(trim($sLang)));
|
|
$sDictFile = $sTempTargetDir.'/dictionaries/'.$sSafeLang.'.dict.php';
|
|
file_put_contents($sDictFile, $sPHPDict);
|
|
}
|
|
$sLanguagesFile = $sTempTargetDir.'/dictionaries/languages.php';
|
|
$sLanguagesDump = var_export($aLanguages, true);
|
|
$sLanguagesFileContent =
|
|
<<<EOF
|
|
<?php
|
|
//
|
|
// Dictionary index built by the compiler
|
|
//
|
|
Dict::SetLanguagesList(
|
|
$sLanguagesDump
|
|
);
|
|
EOF;
|
|
|
|
file_put_contents($sLanguagesFile, $sLanguagesFileContent);
|
|
}
|
|
|
|
protected static function FilterDictString($s)
|
|
{
|
|
if (strpos($s, '~') !== false)
|
|
{
|
|
return str_replace(array('~~', '~*'), '', $s);
|
|
}
|
|
return $s;
|
|
}
|
|
|
|
/**
|
|
* Transform the file references into the corresponding filename (and create the file in the relevant directory)
|
|
*
|
|
* @param \MFElement $oNode
|
|
* @param string $sTempTargetDir
|
|
* @param string $sFinalTargetDir
|
|
* @param string $sRelativePath
|
|
*
|
|
* @throws \DOMFormatException
|
|
* @throws \Exception
|
|
*/
|
|
protected function CompileFiles($oNode, $sTempTargetDir, $sFinalTargetDir, $sRelativePath)
|
|
{
|
|
$oFileRefs = $oNode->GetNodes(".//fileref");
|
|
foreach ($oFileRefs as $oFileRef)
|
|
{
|
|
$sFileId = $oFileRef->getAttribute('ref');
|
|
if ($sFileId !== '')
|
|
{
|
|
$oNodes = $this->oFactory->GetNodes("/itop_design/files/file[@id='$sFileId']");
|
|
if ($oNodes->length == 0)
|
|
{
|
|
throw new DOMFormatException('Could not find the file with ref '.$sFileId);
|
|
}
|
|
|
|
$sName = $oNodes->item(0)->GetChildText('name');
|
|
$sData = base64_decode($oNodes->item(0)->GetChildText('data'));
|
|
$aPathInfo = pathinfo($sName);
|
|
$sFile = $sFileId.'.'.$aPathInfo['extension'];
|
|
$sFilePath = $sTempTargetDir.'/images/'.$sFile;
|
|
@mkdir($sTempTargetDir.'/images');
|
|
file_put_contents($sFilePath, $sData);
|
|
if (!file_exists($sFilePath))
|
|
{
|
|
throw new Exception('Could not write icon file '.$sFilePath);
|
|
}
|
|
$oParentNode = $oFileRef->parentNode;
|
|
$oParentNode->removeChild($oFileRef);
|
|
|
|
$oTextNode = $oParentNode->ownerDocument->createTextNode($sRelativePath.'/images/'.$sFile);
|
|
$oParentNode->appendChild($oTextNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @param \MFElement $oBrandingNode
|
|
* @param string $sTempTargetDir
|
|
* @param string $sFinalTargetDir
|
|
* @param string $sNodeName
|
|
* @param string $sTargetFile
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
protected function CompileLogo($oBrandingNode, $sTempTargetDir, $sFinalTargetDir, $sNodeName, $sTargetFile)
|
|
{
|
|
$sIcon = trim($oBrandingNode->GetChildText($sNodeName));
|
|
if (strlen($sIcon) > 0) {
|
|
$sSourceFile = $sTempTargetDir.'/'.$sIcon;
|
|
$aIconName=explode(".", $sIcon);
|
|
$sIconExtension=$aIconName[count($aIconName)-1];
|
|
$sTargetFile = '/branding/'.$sTargetFile.'.'.$sIconExtension;
|
|
|
|
if (!file_exists($sSourceFile))
|
|
{
|
|
throw new Exception("Branding $sNodeName: could not find the file $sIcon ($sSourceFile)");
|
|
}
|
|
|
|
copy($sSourceFile, $sTempTargetDir.$sTargetFile);
|
|
return $sTargetFile;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param \MFElement $oBrandingNode
|
|
* @param string $sTempTargetDir
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
protected function CompileThemes($oBrandingNode, $sTempTargetDir)
|
|
{
|
|
// Make sure temp. target dir. ends with a '/'
|
|
$sTempTargetDir .= '/';
|
|
|
|
// Set imports paths
|
|
// Note: During compilation, we don't have access to "env-xxx", so we have to set several imports paths:
|
|
// - The CSS directory for the native imports (eg. "../css/css-variables.scss")
|
|
// - The SCSS from modules
|
|
$aImportsPaths = array(
|
|
APPROOT.'css/',
|
|
APPROOT.'css/backoffice/main.scss',
|
|
$sTempTargetDir.'/',
|
|
);
|
|
|
|
// Build compiled themes folder
|
|
$sThemesRelDirPath = 'branding/themes/';
|
|
$sThemesAbsDirPath = $sTempTargetDir.$sThemesRelDirPath;
|
|
if(!is_dir($sThemesAbsDirPath))
|
|
{
|
|
SetupUtils::builddir($sThemesAbsDirPath);
|
|
}
|
|
|
|
// Prepare DM CSS rules for inclusion
|
|
$sDmStylesheetFilename = 'datamodel-compiled-scss-rules.scss';
|
|
$sDmStylesheetContent = implode("\n", $this->aClassesCSSRules);
|
|
$sDmStylesheetId = 'datamodel-compiled-scss-rules';
|
|
$this->WriteFile($sThemesAbsDirPath.$sDmStylesheetFilename, $sDmStylesheetContent);
|
|
|
|
// Parsing theme from common theme node
|
|
/** @var \MFElement $oThemesCommonNodes */
|
|
$oThemesCommonNodes = $oBrandingNode->GetUniqueElement('themes_common', false);
|
|
$aThemesCommonParameters = array(
|
|
'variables' => array(),
|
|
'variable_imports' => array(),
|
|
'utility_imports' => array(),
|
|
'stylesheets' => array(),
|
|
);
|
|
|
|
if($oThemesCommonNodes !== null) {
|
|
/** @var \DOMNodeList $oThemesCommonVariables */
|
|
$oThemesCommonVariables = $oThemesCommonNodes->GetNodes('variables/variable');
|
|
foreach ($oThemesCommonVariables as $oVariable) {
|
|
$sVariableId = $oVariable->getAttribute('id');
|
|
$aThemesCommonParameters['variables'][$sVariableId] = $oVariable->GetText();
|
|
}
|
|
|
|
/** @var \DOMNodeList $oThemesCommonImports */
|
|
$oThemesCommonImports = $oThemesCommonNodes->GetNodes('imports/import');
|
|
foreach ($oThemesCommonImports as $oImport) {
|
|
$sImportId = $oImport->getAttribute('id');
|
|
$sImportType = $oImport->getAttribute('xsi:type');
|
|
if ($sImportType === 'variables') {
|
|
$aThemesCommonParameters['variable_imports'][$sImportId] = $oImport->GetText();
|
|
} elseif ($sImportType === 'utilities') {
|
|
$aThemesCommonParameters['utility_imports'][$sImportId] = $oImport->GetText();
|
|
} else {
|
|
SetupLog::Warning('CompileThemes: Theme common has an import (#'.$sImportId.') without explicit xsi:type, it will be ignored. Check Datamodel XML Reference to fix it.');
|
|
}
|
|
}
|
|
|
|
// Stylesheets
|
|
// - Manually added in the XML
|
|
/** @var \DOMNodeList $oThemesCommonStylesheets */
|
|
$oThemesCommonStylesheets = $oThemesCommonNodes->GetNodes('stylesheets/stylesheet');
|
|
foreach ($oThemesCommonStylesheets as $oStylesheet) {
|
|
$sStylesheetId = $oStylesheet->getAttribute('id');
|
|
$aThemesCommonParameters['stylesheets'][$sStylesheetId] = $oStylesheet->GetText();
|
|
}
|
|
}
|
|
|
|
// Parsing themes from DM
|
|
$aThemes = array();
|
|
/** @var \DOMNodeList $oThemeNodes */
|
|
$oThemeNodes = $oBrandingNode->GetNodes('themes/theme');
|
|
foreach($oThemeNodes as $oTheme)
|
|
{
|
|
$sThemeId = $oTheme->getAttribute('id');
|
|
$aThemeParameters = array(
|
|
'variables' => array(),
|
|
'variable_imports' => array(),
|
|
'utility_imports' => array(),
|
|
'stylesheets' => array(),
|
|
);
|
|
|
|
/** @var \DOMNodeList $oVariables */
|
|
$oVariables = $oTheme->GetNodes('variables/variable');
|
|
foreach ($oVariables as $oVariable) {
|
|
$sVariableId = $oVariable->getAttribute('id');
|
|
$aThemeParameters['variables'][$sVariableId] = $oVariable->GetText();
|
|
}
|
|
|
|
/** @var \DOMNodeList $oImports */
|
|
$oImports = $oTheme->GetNodes('imports/import');
|
|
foreach ($oImports as $oImport) {
|
|
$sImportId = $oImport->getAttribute('id');
|
|
$sImportType = $oImport->getAttribute('xsi:type');
|
|
if ($sImportType === 'variables') {
|
|
$aThemeParameters['variable_imports'][$sImportId] = $oImport->GetText();
|
|
} elseif ($sImportType === 'utilities') {
|
|
$aThemeParameters['utility_imports'][$sImportId] = $oImport->GetText();
|
|
} else {
|
|
SetupLog::Warning('CompileThemes: Theme #'.$sThemeId.' has an import (#'.$sImportId.') without explicit xsi:type, it will be ignored. Check Datamodel XML Reference to fix it.');
|
|
}
|
|
}
|
|
|
|
// Stylesheets
|
|
// - Manually added in the XML
|
|
/** @var \DOMNodeList $oStylesheets */
|
|
$oStylesheets = $oTheme->GetNodes('stylesheets/stylesheet');
|
|
foreach($oStylesheets as $oStylesheet)
|
|
{
|
|
$sStylesheetId = $oStylesheet->getAttribute('id');
|
|
$aThemeParameters['stylesheets'][$sStylesheetId] = $oStylesheet->GetText();
|
|
}
|
|
|
|
// - Computed from the DM
|
|
$aThemeParameters['stylesheets'][$sDmStylesheetId] = $sThemesRelDirPath.$sDmStylesheetFilename;
|
|
|
|
// - Overload default values with module ones
|
|
foreach ($aThemeParameters as $sThemeParameterName => $aThemeParameter) {
|
|
if(array_key_exists($sThemeParameterName, $aThemesCommonParameters)){
|
|
$aThemeParameters[$sThemeParameterName] = array_merge($aThemeParameter, $aThemesCommonParameters[$sThemeParameterName]);
|
|
}
|
|
}
|
|
|
|
$aThemes[$sThemeId] = [
|
|
'theme_parameters' => $aThemeParameters,
|
|
'precompiled_stylesheet' => $oTheme->GetChildText('precompiled_stylesheet', '')
|
|
];
|
|
}
|
|
|
|
// Force to have a default theme if none in the DM
|
|
if(empty($aThemes))
|
|
{
|
|
$aDefaultThemeInfo = ThemeHandler::GetDefaultThemeInformation();
|
|
$aDefaultThemeInfo['parameters']['stylesheets'][$sDmStylesheetId] = $sThemesRelDirPath.$sDmStylesheetFilename;
|
|
$aThemes[$aDefaultThemeInfo['name']] = $aDefaultThemeInfo['parameters'];
|
|
}
|
|
|
|
$sPostCompilationPrecompiledThemeFolder = APPROOT . self::DATA_PRECOMPILED_FOLDER;
|
|
if (! is_dir($sPostCompilationPrecompiledThemeFolder)){
|
|
mkdir($sPostCompilationPrecompiledThemeFolder);
|
|
}
|
|
|
|
// Compile themes
|
|
$fStart = microtime(true);
|
|
foreach($aThemes as $sThemeId => $aThemeInfos)
|
|
{
|
|
$aThemeParameters = $aThemeInfos['theme_parameters'];
|
|
$sPrecompiledStylesheet = $aThemeInfos['precompiled_stylesheet'];
|
|
|
|
$sThemeDir = $sThemesAbsDirPath.$sThemeId;
|
|
if(!is_dir($sThemeDir))
|
|
{
|
|
SetupUtils::builddir($sThemeDir);
|
|
}
|
|
|
|
// Check if a precompiled version of the theme is supplied
|
|
$sPostCompilationLatestPrecompiledFile = $sPostCompilationPrecompiledThemeFolder . $sThemeId . ".css";
|
|
|
|
$sPrecompiledFileToUse = $this->UseLatestPrecompiledFile($sTempTargetDir, $sPrecompiledStylesheet, $sPostCompilationLatestPrecompiledFile, $sThemeId);
|
|
if ($sPrecompiledFileToUse != null){
|
|
copy($sPrecompiledFileToUse, $sThemeDir.'/main.css');
|
|
// Make sure that the copy of the precompiled file is older than any other files to force a validation of the signature
|
|
touch($sThemeDir.'/main.css', 1577836800 /* 2020-01-01 00:00:00 */);
|
|
}
|
|
|
|
if (!static::$oThemeHandlerService) {
|
|
static::$oThemeHandlerService = new ThemeHandlerService();
|
|
}
|
|
$bHasCompiled = static::$oThemeHandlerService->CompileTheme($sThemeId, true, $this->sCompilationTimeStamp, $aThemeParameters, $aImportsPaths, $sTempTargetDir);
|
|
|
|
if ($bHasCompiled) {
|
|
if (utils::GetConfig()->Get('theme.enable_precompilation')){
|
|
/*if (utils::IsDevelopmentEnvironment() && ! empty(trim($sPrecompiledStylesheet))) //N°4438 - Disable (temporary) copy of precompiled stylesheets after setup
|
|
{ //help developers to detect & push theme precompilation changes
|
|
$sInitialPrecompiledFilePath = null;
|
|
$aRootDirs = $this->oFactory->GetRootDirs();
|
|
if (is_array($aRootDirs) && count($aRootDirs) !== 0) {
|
|
foreach ($this->oFactory->GetRootDirs() as $sRootDir) {
|
|
$sCurrentFile = $sRootDir. DIRECTORY_SEPARATOR . $sPrecompiledStylesheet;
|
|
if (is_file($sCurrentFile) && is_writable($sCurrentFile)) {
|
|
$sInitialPrecompiledFilePath = $sCurrentFile;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($sInitialPrecompiledFilePath != null){
|
|
SetupLog::Info("Replacing theme '$sThemeId' precompiled file in file $sInitialPrecompiledFilePath for next setup.");
|
|
copy($sThemeDir.'/main.css', $sInitialPrecompiledFilePath);
|
|
}
|
|
}*/
|
|
|
|
SetupLog::Info("Replacing theme '$sThemeId' precompiled file in file $sPostCompilationLatestPrecompiledFile for next setup.");
|
|
copy($sThemeDir.'/main.css', $sPostCompilationLatestPrecompiledFile);
|
|
}
|
|
} else {
|
|
SetupLog::Info("No theme '$sThemeId' compilation was required during setup.");
|
|
}
|
|
}
|
|
$this->Log(sprintf('Themes compilation took: %.3f ms for %d themes.', (microtime(true) - $fStart)*1000.0, count($aThemes)));
|
|
}
|
|
|
|
public static function SetThemeHandlerService(ThemeHandlerService $oThemeHandlerService): void {
|
|
self::$oThemeHandlerService = $oThemeHandlerService;
|
|
}
|
|
|
|
/**
|
|
* Choose between precompiled files declared in datamodel XMLs or latest precompiled files generated after latest setup.
|
|
*
|
|
* @param string $sTempTargetDir
|
|
* @param string $sPrecompiledFileUri
|
|
* @param string $sPostCompilationLatestPrecompiledFile
|
|
* @param string $sThemeId
|
|
*
|
|
* @return string : file path of latest precompiled file to use for setup
|
|
*/
|
|
public function UseLatestPrecompiledFile(string $sTempTargetDir, string $sPrecompiledFileUri, $sPostCompilationLatestPrecompiledFile, $sThemeId) : ?string {
|
|
if (! utils::GetConfig()->Get('theme.enable_precompilation')) {
|
|
return null;
|
|
}
|
|
|
|
$bDataXmlPrecompiledFileExists = false;
|
|
clearstatcache();
|
|
if (!empty($sPrecompiledFileUri)){
|
|
$sDataXmlProvidedPrecompiledFile = $sTempTargetDir . DIRECTORY_SEPARATOR . $sPrecompiledFileUri;
|
|
$bDataXmlPrecompiledFileExists = file_exists($sDataXmlProvidedPrecompiledFile) ;
|
|
if (!$bDataXmlPrecompiledFileExists){
|
|
SetupLog::Warning("Missing defined theme '$sThemeId' precompiled file configured with: '$sPrecompiledFileUri'");
|
|
} else {
|
|
$sSourceDir = APPROOT . utils::GetConfig()->Get('source_dir');
|
|
|
|
$aDirToCheck = [
|
|
$sSourceDir,
|
|
APPROOT . DIRECTORY_SEPARATOR . 'extensions/'
|
|
];
|
|
|
|
$iDataXmlFileLastModified = 0;
|
|
foreach ($aDirToCheck as $sDir){
|
|
$sCurrentFile = $sDir . DIRECTORY_SEPARATOR . $sPrecompiledFileUri;
|
|
if (is_file($sCurrentFile)){
|
|
$iDataXmlFileLastModified = max($iDataXmlFileLastModified, @filemtime($sCurrentFile));
|
|
}
|
|
}
|
|
|
|
if ($iDataXmlFileLastModified == 0){
|
|
SetupLog::Warning("Missing defined theme '$sThemeId' precompiled file in datamodels/X.x or extensions directory configured with: '$sPrecompiledFileUri'. That should not happen!");
|
|
$bDataXmlPrecompiledFileExists = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
$bPostCompilationPrecompiledFileExists = file_exists($sPostCompilationLatestPrecompiledFile);
|
|
|
|
if (!$bDataXmlPrecompiledFileExists && !$bPostCompilationPrecompiledFileExists){
|
|
return null;
|
|
}
|
|
|
|
if (!$bDataXmlPrecompiledFileExists){
|
|
$sPrecompiledFileToUse = $sPostCompilationLatestPrecompiledFile;
|
|
} else if (!$bPostCompilationPrecompiledFileExists){
|
|
$sPrecompiledFileToUse = $sDataXmlProvidedPrecompiledFile;
|
|
} else{
|
|
$iPostCompilationFileLastModified = @filemtime($sPostCompilationLatestPrecompiledFile);
|
|
SetupLog::Debug("Theme '$sThemeId' check mtime between data XML file " . $iDataXmlFileLastModified . " and latest postcompilation file: " . $iPostCompilationFileLastModified);
|
|
|
|
$sPrecompiledFileToUse = $iDataXmlFileLastModified > $iPostCompilationFileLastModified ? $sDataXmlProvidedPrecompiledFile : $sPostCompilationLatestPrecompiledFile;
|
|
}
|
|
|
|
SetupLog::Info("For theme '$sThemeId' precompiled file used: '$sPrecompiledFileToUse'");
|
|
return $sPrecompiledFileToUse;
|
|
}
|
|
|
|
/**
|
|
* @param \MFElement $oBrandingNode
|
|
* @param string $sTempTargetDir
|
|
* @param string $sFinalTargetDir
|
|
*
|
|
* @throws \DOMFormatException
|
|
* @throws \Exception
|
|
*/
|
|
protected function CompileBranding($oBrandingNode, $sTempTargetDir, $sFinalTargetDir)
|
|
{
|
|
// Enable relative paths
|
|
SetupUtils::builddir($sTempTargetDir.'/branding');
|
|
if ($oBrandingNode)
|
|
{
|
|
// Transform file refs into files in the images folder
|
|
$this->CompileFiles($oBrandingNode, $sTempTargetDir.'/branding', $sFinalTargetDir.'/branding', 'branding');
|
|
$aDataBranding = [];
|
|
|
|
$aLogosToCompile = [
|
|
['sNodeName' => 'login_logo', 'sTargetFile' => 'login-logo', 'sType' => Branding::ENUM_LOGO_TYPE_LOGIN_LOGO],
|
|
['sNodeName' => 'main_logo', 'sTargetFile' => 'main-logo-full', 'sType' => Branding::ENUM_LOGO_TYPE_MAIN_LOGO_FULL],
|
|
['sNodeName' => 'main_logo_compact', 'sTargetFile' => 'main-logo-compact', 'sType' => Branding::ENUM_LOGO_TYPE_MAIN_LOGO_COMPACT],
|
|
['sNodeName' => 'portal_logo', 'sTargetFile' =>'portal-logo', 'sType' => Branding::ENUM_LOGO_TYPE_PORTAL_LOGO],
|
|
];
|
|
foreach ($aLogosToCompile as $aLogo) {
|
|
$sLogo = $this->CompileLogo($oBrandingNode, $sTempTargetDir, $sFinalTargetDir, $aLogo['sNodeName'], $aLogo['sTargetFile']);
|
|
if ($sLogo != null) {
|
|
$aDataBranding[$aLogo['sType']] = $sLogo;
|
|
}
|
|
}
|
|
if ($sTempTargetDir == null) {
|
|
$sWorkingPath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/';
|
|
} else {
|
|
$sWorkingPath = $sTempTargetDir;
|
|
}
|
|
|
|
file_put_contents($sWorkingPath.'/branding/logos.json', json_encode($aDataBranding));
|
|
|
|
// Cleanup the images directory (eventually made by CompileFiles)
|
|
if (file_exists($sTempTargetDir.'/branding/images'))
|
|
{
|
|
SetupUtils::rrmdir($sTempTargetDir.'/branding/images');
|
|
}
|
|
|
|
// Compile themes
|
|
$this->CompileThemes($oBrandingNode, $sTempTargetDir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param \MFElement $oPortalsNode
|
|
* @param string $sTempTargetDir
|
|
* @param string $sFinalTargetDir
|
|
*/
|
|
protected function CompilePortals($oPortalsNode, $sTempTargetDir, $sFinalTargetDir)
|
|
{
|
|
if ($oPortalsNode)
|
|
{
|
|
// Create some static PHP data in <env-xxx>/core/main.php
|
|
$oPortals = $oPortalsNode->GetNodes('portal');
|
|
$aPortalsConfig = array();
|
|
foreach($oPortals as $oPortal)
|
|
{
|
|
$sPortalId = $oPortal->getAttribute('id');
|
|
$aPortalsConfig[$sPortalId] = array();
|
|
$aPortalsConfig[$sPortalId]['rank'] = (float)$oPortal->GetChildText('rank', 0);
|
|
$aPortalsConfig[$sPortalId]['handler'] = $oPortal->GetChildText('handler', 'PortalDispatcher');
|
|
$aPortalsConfig[$sPortalId]['url'] = $oPortal->GetChildText('url', 'portal/index.php');
|
|
$oAllow = $oPortal->GetOptionalElement('allow');
|
|
$aPortalsConfig[$sPortalId]['allow'] = array();
|
|
if ($oAllow)
|
|
{
|
|
foreach($oAllow->GetNodes('profile') as $oProfile)
|
|
{
|
|
$aPortalsConfig[$sPortalId]['allow'][] = $oProfile->getAttribute('id');
|
|
}
|
|
}
|
|
$oDeny = $oPortal->GetOptionalElement('deny');
|
|
$aPortalsConfig[$sPortalId]['deny'] = array();
|
|
if ($oDeny)
|
|
{
|
|
foreach($oDeny->GetNodes('profile') as $oProfile)
|
|
{
|
|
$aPortalsConfig[$sPortalId]['deny'][] = $oProfile->getAttribute('id');
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = ".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";
|
|
$this->sMainPHPCode .= "\t\tif (!array_key_exists(\$sPortalId, self::\$aData)) return array();\n";
|
|
$this->sMainPHPCode .= "\t\treturn self::\$aData[\$sPortalId];\n";
|
|
$this->sMainPHPCode .= "\t}\n";
|
|
$this->sMainPHPCode .= "}\n";
|
|
}
|
|
}
|
|
|
|
public static function SortOnRank($aConf1, $aConf2)
|
|
{
|
|
return ($aConf1['rank'] < $aConf2['rank']) ? -1 : 1;
|
|
}
|
|
|
|
/**
|
|
* @param \MFElement $oParametersNode
|
|
* @param string $sTempTargetDir
|
|
* @param string $sFinalTargetDir
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
protected function CompileParameters($oParametersNode, $sTempTargetDir, $sFinalTargetDir)
|
|
{
|
|
if ($oParametersNode)
|
|
{
|
|
// Create some static PHP data in <env-xxx>/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";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $oDesigns
|
|
* @param $sTempTargetDir
|
|
* @param $sFinalTargetDir
|
|
*
|
|
* @throws \DOMFormatException
|
|
* @throws \Exception
|
|
*/
|
|
protected function CompileModuleDesigns($oDesigns, $sTempTargetDir, $sFinalTargetDir)
|
|
{
|
|
if ($oDesigns)
|
|
{
|
|
SetupUtils::builddir($sTempTargetDir.'/core/module_designs/images');
|
|
$this->CompileFiles($oDesigns, $sTempTargetDir.'/core/module_designs', $sFinalTargetDir.'/core/module_designs', 'core/module_designs');
|
|
foreach ($oDesigns->GetNodes('module_design') as $oDesign)
|
|
{
|
|
$oDoc = new ModuleDesign();
|
|
$oClone = $oDoc->importNode($oDesign->cloneNode(true), true);
|
|
$oDoc->appendChild($oClone);
|
|
$oDoc->save($sTempTargetDir.'/core/module_designs/'.$oDesign->getAttribute('id').'.xml');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \DOMFormatException
|
|
*/
|
|
protected function LoadSnippets()
|
|
{
|
|
$oSnippets = $this->oFactory->GetNodes('/itop_design/snippets/snippet');
|
|
foreach($oSnippets as $oSnippet)
|
|
{
|
|
$sSnippetId = $oSnippet->getAttribute('id');
|
|
$sPlacement = $oSnippet->GetChildText('placement', null);
|
|
if ($sPlacement == 'core')
|
|
{
|
|
$sModuleId = '_core_';
|
|
}
|
|
else if ($sPlacement == 'module')
|
|
{
|
|
$sModuleId = $oSnippet->GetChildText('module', null);
|
|
if ($sModuleId == null)
|
|
{
|
|
throw new DOMFormatException("Invalid definition for snippet id='$sSnippetId' with placement=module. Missing '<module>' tag.");
|
|
}
|
|
}
|
|
else if ($sPlacement === 'null')
|
|
{
|
|
throw new DOMFormatException("Invalid definition for snippet id='$sSnippetId'. Missing <placement> tag.");
|
|
}
|
|
else
|
|
{
|
|
throw new DOMFormatException("Invalid definition for snippet id='$sSnippetId'. Incorrect value '$sPlacement' for <placement> tag. The allowed values are either 'core' or 'module'.");
|
|
}
|
|
if (!array_key_exists($sModuleId, $this->aSnippets))
|
|
{
|
|
$this->aSnippets[$sModuleId] = array('before' => array(), 'after' => array());
|
|
}
|
|
|
|
$fOrder = (float) $oSnippet->GetChildText('rank', 0);
|
|
$sContent = $oSnippet->GetChildText('content', '');
|
|
if ($fOrder < 0)
|
|
{
|
|
$this->aSnippets[$sModuleId]['before'][] = array(
|
|
'rank' => $fOrder,
|
|
'content' => $sContent,
|
|
'snippet_id' => $sSnippetId,
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$this->aSnippets[$sModuleId]['after'][] = array(
|
|
'rank' => $fOrder,
|
|
'content' => $sContent,
|
|
'snippet_id' => $sSnippetId,
|
|
);
|
|
}
|
|
}
|
|
foreach($this->aSnippets as $sModuleId => $void)
|
|
{
|
|
uasort($this->aSnippets[$sModuleId]['before'], array(get_class($this), 'SortOnRank'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* We can't use var_export() as we need to output some PHP code, for example `utils::GetAbsoluteUrlModulesRoot()` calls
|
|
*
|
|
* @param string[string] $aAssocArray
|
|
*
|
|
* @return string PHP declaration of the array
|
|
*/
|
|
private function GetAssociativeArrayAsPhpCode($aAssocArray)
|
|
{
|
|
$aArrayPhp = array();
|
|
foreach ($aAssocArray as $sKey => $sPHPValue)
|
|
{
|
|
$aArrayPhp[] = " '$sKey' => $sPHPValue,";
|
|
}
|
|
$sArrayPhp = implode("\n", $aArrayPhp);
|
|
|
|
return 'array('.$sArrayPhp.')';
|
|
}
|
|
|
|
/**
|
|
* @param string $sClassName
|
|
* @param string $sParentClassName
|
|
* @param string $sClassParams serialized array. Use ::GetAssociativeArrayAsPhpCode if you need to keep some PHP code calls
|
|
* @param string $sInitMethodCalls
|
|
* @param bool $bIsAbstractClass
|
|
* @param string $sMethods
|
|
*
|
|
* @param array $aRequiredFiles
|
|
* @param string $sCodeComment
|
|
*
|
|
* @return string php code for the class
|
|
*/
|
|
private function GeneratePhpCodeForClass(
|
|
$sClassName,
|
|
$sParentClassName,
|
|
$sClassParams,
|
|
$sInitMethodCalls = '',
|
|
$bIsAbstractClass = false,
|
|
$sMethods = '',
|
|
$aRequiredFiles = [],
|
|
$sCodeComment = ''
|
|
) {
|
|
$sPHP = "\n\n$sCodeComment\n";
|
|
|
|
foreach ($aRequiredFiles as $sIncludeFile)
|
|
{
|
|
$sPHP .= "\nrequire_once('$sIncludeFile');\n";
|
|
}
|
|
|
|
if ($bIsAbstractClass)
|
|
{
|
|
$sPHP .= 'abstract class '.$sClassName;
|
|
}
|
|
else
|
|
{
|
|
$sPHP .= 'class '.$sClassName;
|
|
}
|
|
$sPHP .= " extends $sParentClassName\n";
|
|
$sPHP .=
|
|
<<<EOF
|
|
{
|
|
public static function Init()
|
|
{
|
|
\$aParams = $sClassParams;
|
|
MetaModel::Init_Params(\$aParams);
|
|
MetaModel::Init_InheritAttributes();
|
|
$sInitMethodCalls
|
|
}
|
|
|
|
$sMethods
|
|
}
|
|
EOF;
|
|
|
|
return $sPHP;
|
|
}
|
|
|
|
/**
|
|
* Write a file only if not exists
|
|
* Also add some informations when write failure occurs
|
|
*
|
|
* @param string $sFilename
|
|
* @param string $sContent
|
|
* @param int $flags
|
|
*
|
|
* @return bool|int
|
|
* @throws \Exception
|
|
*
|
|
* @uses \unlink()
|
|
* @uses \file_put_contents()
|
|
*
|
|
* @since 3.0.0 The file is removed before writing (commit c5d265f6)
|
|
* For now this causes model.*.php files to always be located in env-* dir, even if symlinks are enabled
|
|
* See N°4854
|
|
* @link https://www.itophub.io/wiki/page?id=3_0_0%3Arelease%3A3_0_whats_new#compiler_always_generate_new_model_php compiler behavior change documentation
|
|
*/
|
|
protected function WriteFile($sFilename, $sContent, $flags = null)
|
|
{
|
|
if (is_file($sFilename) || is_link($sFilename))
|
|
{
|
|
@unlink($sFilename);
|
|
}
|
|
$ret = file_put_contents($sFilename, $sContent, $flags);
|
|
if ($ret === false)
|
|
{
|
|
$iLen = strlen($sContent);
|
|
$fFree = @disk_free_space(dirname($sFilename));
|
|
$aErr = error_get_last();
|
|
throw new Exception("Failed to write '$sFilename'. Last error: '{$aErr['message']}', content to write: $iLen bytes, available free space on disk: $fFree.");
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* if no ".htaccess" is present, add a generic one prohibiting access to potentially sensible files (ie: even if it is quite a bad practice, it may happen that a developer put a secret into the xml)
|
|
*
|
|
* @param $sTempTargetDir
|
|
* @param $sFinalTargetDir
|
|
* @param $sRelativeDir
|
|
*
|
|
* @throws \Exception
|
|
*
|
|
* @since 2.7.0 N°2498
|
|
*/
|
|
protected function WriteStaticOnlyHtaccess($sTempTargetDir)
|
|
{
|
|
$sContent = <<<EOF
|
|
|
|
# Apache 2.4
|
|
<ifModule mod_authz_core.c>
|
|
Require all denied
|
|
<FilesMatch ".+\.(css|scss|js|map|png|bmp|gif|jpe?g|svg|tiff|woff2?|ttf|eot|html|php)$">
|
|
Require all granted
|
|
</FilesMatch>
|
|
</ifModule>
|
|
|
|
# Apache 2.2
|
|
<ifModule !mod_authz_core.c>
|
|
deny from all
|
|
Satisfy All
|
|
<FilesMatch ".+\.(css|scss|js|map|png|bmp|gif|jpe?g|svg|tiff|woff2?|ttf|eot|html|php)$">
|
|
Order Allow,Deny
|
|
Allow from all
|
|
</FilesMatch>
|
|
</ifModule>
|
|
|
|
# Apache 2.2 and 2.4
|
|
IndexIgnore *
|
|
|
|
EOF;
|
|
|
|
$this->WriteFile("$sTempTargetDir/.htaccess", $sContent);
|
|
}
|
|
|
|
/**
|
|
* if no "web.config" is present, add a generic one prohibiting access to potentially sensible files (ie: even if it is quite a bad practice, it may happen that a developer put a secret into the xml)
|
|
*
|
|
* @param $sTempTargetDir
|
|
* @param $sFinalTargetDir
|
|
* @param $sRelativeDir
|
|
* @param $sModuleName
|
|
* @param $sModuleVersion
|
|
*
|
|
* @throws \Exception
|
|
*
|
|
* @since 2.7.0 N°2498
|
|
*/
|
|
protected function WriteStaticOnlyWebConfig($sTempTargetDir)
|
|
{
|
|
$sContent = <<<XML
|
|
<?xml version="1.0" encoding="utf-8" ?>
|
|
<configuration>
|
|
<system.webServer>
|
|
<security>
|
|
<requestFiltering>
|
|
<fileExtensions applyToWebDAV="false" allowUnlisted="false" >
|
|
<add fileExtension=".css" allowed="true" />
|
|
<add fileExtension=".scss" allowed="true" />
|
|
<add fileExtension=".js" allowed="true" />
|
|
<add fileExtension=".map" allowed="true" />
|
|
<add fileExtension=".png" allowed="true" />
|
|
<add fileExtension=".bmp" allowed="true" />
|
|
<add fileExtension=".gif" allowed="true" />
|
|
<add fileExtension=".jpeg" allowed="true" />
|
|
<add fileExtension=".jpg" allowed="true" />
|
|
<add fileExtension=".svg" allowed="true" />
|
|
<add fileExtension=".tiff" allowed="true" />
|
|
|
|
<add fileExtension=".woff" allowed="true" />
|
|
<add fileExtension=".woff2" allowed="true" />
|
|
<add fileExtension=".ttf" allowed="true" />
|
|
<add fileExtension=".eot" allowed="true" />
|
|
|
|
<add fileExtension=".html" allowed="true" />
|
|
|
|
<add fileExtension=".php" allowed="true" />
|
|
</fileExtensions>
|
|
</requestFiltering>
|
|
</security>
|
|
</system.webServer>
|
|
</configuration>
|
|
|
|
XML;
|
|
|
|
$this->WriteFile("$sTempTargetDir/web.config", $sContent);
|
|
}
|
|
|
|
/**
|
|
* @param $sResultFile
|
|
* @param $sModuleName
|
|
* @param $sModuleVersion
|
|
* @param $sCompiledCode
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
protected function WritePHPFile($sResultFile, $sModuleName, $sModuleVersion, $sCompiledCode)
|
|
{
|
|
if (is_file($sResultFile))
|
|
{
|
|
$this->Log("Updating $sResultFile for module $sModuleName in version $sModuleVersion");
|
|
}
|
|
else
|
|
{
|
|
$sResultDir = dirname($sResultFile);
|
|
if (!is_dir($sResultDir))
|
|
{
|
|
$this->Log("Creating directory $sResultDir");
|
|
mkdir($sResultDir, 0777, true);
|
|
}
|
|
$this->Log("Creating $sResultFile for module $sModuleName in version $sModuleVersion");
|
|
}
|
|
|
|
// Compile the module into a single file
|
|
//
|
|
$sCurrDate = date(DATE_ISO8601);
|
|
$sAuthor = 'iTop compiler';
|
|
$sLicence = 'http://opensource.org/licenses/AGPL-3.0';
|
|
$sFileHeader =
|
|
<<<EOF
|
|
<?php
|
|
//
|
|
// File generated by ... on the $sCurrDate
|
|
// Please do not edit manually
|
|
//
|
|
|
|
/**
|
|
* Classes and menus for $sModuleName (version $sModuleVersion)
|
|
*
|
|
* @author $sAuthor
|
|
* @license $sLicence
|
|
*/
|
|
|
|
EOF;
|
|
$this->WriteFile($sResultFile, $sFileHeader.$sCompiledCode);
|
|
}
|
|
|
|
private static function RemoveSurroundingQuotes($sValue)
|
|
{
|
|
if (utils::StartsWith($sValue, '\'') && utils::EndsWith($sValue, '\''))
|
|
{
|
|
$sValue = substr($sValue, 1, -1);
|
|
}
|
|
|
|
return $sValue;
|
|
}
|
|
}
|