diff --git a/core/datamodel.core.xml b/core/datamodel.core.xml index 872b4cef8..11d6d03d8 100644 --- a/core/datamodel.core.xml +++ b/core/datamodel.core.xml @@ -1185,5 +1185,106 @@ + + + Dashlet + + + + + + + + + + + + + + + + + + + {{query.selected_class}} + + + + + + bars + + + + + + + + + + + + + + + + + + + + + + {{query.selected_class}} + + + + + {{aggregation_function.value != 'count'}} + + + + + {{query.selected_class}} + numeric + + + + + + + + + + + + + + + + + + {{order_by.value = 'function'}} + + + + + + + + + + + + + + + + + + desc + + + + diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 85baff5e5..54856b3b8 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -509,7 +509,7 @@ return array( 'Combodo\\iTop\\Forms\\Block\\IFormBlock' => $baseDir . '/sources/Forms/Block/IFormBlock.php', 'Combodo\\iTop\\Forms\\Compiler\\FormsCompiler' => $baseDir . '/sources/Forms/Compiler/FormsCompiler.php', 'Combodo\\iTop\\Forms\\Compiler\\FormsCompilerException' => $baseDir . '/sources/Forms/Compiler/FormsCompilerException.php', - 'Combodo\\iTop\\Forms\\Compiler\\FormsController' => $baseDir . '/sources/Forms/Compiler/FormsController.php', + 'Combodo\\iTop\\Forms\\Controller\\FormsController' => $baseDir . '/sources/Forms/Controller/FormsController.php', 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => $baseDir . '/sources/Forms/FormBuilder/DependencyHandler.php', 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => $baseDir . '/sources/Forms/FormBuilder/DependencyMap.php', 'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => $baseDir . '/sources/Forms/FormBuilder/FormBuilder.php', @@ -555,6 +555,7 @@ return array( 'Combodo\\iTop\\PropertyTree\\CollectionOfValues' => $baseDir . '/sources/PropertyTree/CollectionOfValues.php', 'Combodo\\iTop\\PropertyTree\\Property' => $baseDir . '/sources/PropertyTree/Property.php', 'Combodo\\iTop\\PropertyTree\\PropertyTree' => $baseDir . '/sources/PropertyTree/PropertyTree.php', + 'Combodo\\iTop\\PropertyTree\\PropertyTreeDesign' => $baseDir . '/sources/PropertyTree/PropertyTreeDesign.php', 'Combodo\\iTop\\PropertyTree\\PropertyTreeException' => $baseDir . '/sources/PropertyTree/PropertyTreeException.php', 'Combodo\\iTop\\PropertyTree\\PropertyTreeFactory' => $baseDir . '/sources/PropertyTree/PropertyTreeFactory.php', 'Combodo\\iTop\\PropertyTree\\ValueType\\AbstractValueType' => $baseDir . '/sources/PropertyTree/ValueType/AbstractValueType.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index e8bb0de1b..50493d8fe 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -895,7 +895,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Forms\\Block\\IFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/IFormBlock.php', 'Combodo\\iTop\\Forms\\Compiler\\FormsCompiler' => __DIR__ . '/../..' . '/sources/Forms/Compiler/FormsCompiler.php', 'Combodo\\iTop\\Forms\\Compiler\\FormsCompilerException' => __DIR__ . '/../..' . '/sources/Forms/Compiler/FormsCompilerException.php', - 'Combodo\\iTop\\Forms\\Compiler\\FormsController' => __DIR__ . '/../..' . '/sources/Forms/Compiler/FormsController.php', + 'Combodo\\iTop\\Forms\\Controller\\FormsController' => __DIR__ . '/../..' . '/sources/Forms/Controller/FormsController.php', 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyHandler.php', 'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyMap.php', 'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormBuilder.php', @@ -941,6 +941,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\PropertyTree\\CollectionOfValues' => __DIR__ . '/../..' . '/sources/PropertyTree/CollectionOfValues.php', 'Combodo\\iTop\\PropertyTree\\Property' => __DIR__ . '/../..' . '/sources/PropertyTree/Property.php', 'Combodo\\iTop\\PropertyTree\\PropertyTree' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTree.php', + 'Combodo\\iTop\\PropertyTree\\PropertyTreeDesign' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeDesign.php', 'Combodo\\iTop\\PropertyTree\\PropertyTreeException' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeException.php', 'Combodo\\iTop\\PropertyTree\\PropertyTreeFactory' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeFactory.php', 'Combodo\\iTop\\PropertyTree\\ValueType\\AbstractValueType' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/AbstractValueType.php', diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 918cf7f49..5ff67e236 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -23,6 +23,7 @@ use Combodo\iTop\Application\WebPage\iTopWebPage; use Combodo\iTop\Application\WebPage\Page; use Combodo\iTop\DesignElement; use Combodo\iTop\DesignDocument; +use Combodo\iTop\PropertyTree\PropertyTreeDesign; require_once(APPROOT.'setup/setuputils.class.inc.php'); require_once(APPROOT.'setup/modelfactory.class.inc.php'); @@ -699,6 +700,10 @@ PHP; $oModuleDesignsNode = $this->oFactory->GetNodes('/itop_design/module_designs')->item(0); $this->CompileModuleDesigns($oModuleDesignsNode, $sTempTargetDir, $sFinalTargetDir); + // Create property trees XML files + $oPropertyTreesNode = $this->oFactory->GetNodes('/itop_design/meta/property_trees')->item(0); + $this->CompilePropertyTrees($oPropertyTreesNode, $sTempTargetDir, $sFinalTargetDir); + // Compile the XML parameters /** @var \MFElement $oParametersNode */ $oParametersNode = $this->oFactory->GetNodes('/itop_design/module_parameters')->item(0); @@ -3572,6 +3577,21 @@ EOF; } } + protected function CompilePropertyTrees(?DOMNode $oPropertyTrees, string $sTempTargetDir, string $sFinalTargetDir): void + { + if ($oPropertyTrees) { + foreach ($oPropertyTrees->GetNodes('property_tree') as $oPropertyTree) { + $oDoc = new PropertyTreeDesign(); + $oClone = $oDoc->importNode($oPropertyTree->cloneNode(true), true); + $oDoc->appendChild($oClone); + /** @var DesignElement $oPropertyTree */ + $sExtends = $oPropertyTree->GetChildText('extends', 'Default'); + SetupUtils::builddir($sTempTargetDir.'/core/property_trees/'.$sExtends); + $oDoc->save($sTempTargetDir.'/core/property_trees/'.$sExtends.'/'.$oPropertyTree->getAttribute('id').'.xml'); + } + } + } + /** * @throws \DOMFormatException */ diff --git a/sources/Application/UI/Base/Component/TurboForm/TurboFormUIBlockFactory.php b/sources/Application/UI/Base/Component/TurboForm/TurboFormUIBlockFactory.php index bb1d0b47d..414d28b0c 100644 --- a/sources/Application/UI/Base/Component/TurboForm/TurboFormUIBlockFactory.php +++ b/sources/Application/UI/Base/Component/TurboForm/TurboFormUIBlockFactory.php @@ -9,8 +9,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\TurboForm; use Combodo\iTop\Application\UI\Base\AbstractUIBlockFactory; use Combodo\iTop\Forms\Block\FormBlockService; -use Combodo\iTop\Forms\Compiler\FormsCompiler; -use Combodo\iTop\Forms\Compiler\FormsController; +use Combodo\iTop\Forms\Controller\FormsController; use Symfony\Component\Form\FormView; use utils; @@ -60,7 +59,7 @@ class TurboFormUIBlockFactory extends AbstractUIBlockFactory */ public static function MakeForDashletConfiguration(string $sDashletId, array $aData = [], string $sId = null): TurboForm { - $oBlockForm = FormBlockService::GetInstance()->GetFormBlockById($sDashletId); + $oBlockForm = FormBlockService::GetInstance()->GetFormBlockById($sDashletId, 'Dashlet'); $oController = new FormsController(); $oBuilder = $oController->GetFormBuilder($oBlockForm, $aData); $oForm = $oBuilder->getForm(); diff --git a/sources/Forms/Block/FormBlockService.php b/sources/Forms/Block/FormBlockService.php index d43f3caa3..6e80d7a50 100644 --- a/sources/Forms/Block/FormBlockService.php +++ b/sources/Forms/Block/FormBlockService.php @@ -13,9 +13,11 @@ use Combodo\iTop\Service\Cache\DataModelDependantCache; use Combodo\iTop\Service\DependencyInjection\DIService; use ModelReflection; use ModelReflectionRuntime; +use utils; class FormBlockService { + public const CACHE_POOL = 'Forms'; private static FormBlockService $oInstance; private DataModelDependantCache $oCacheService; @@ -36,22 +38,29 @@ class FormBlockService /** * @param string $sId name of the form to retrieve + * @param string $sType * * @return \Combodo\iTop\Forms\Block\Base\FormBlock * @throws \Combodo\iTop\Forms\Block\FormBlockException + * @throws \Combodo\iTop\Forms\Compiler\FormsCompilerException + * @throws \Combodo\iTop\PropertyTree\PropertyTreeException + * @throws \DOMFormatException */ - public function GetFormBlockById(string $sId): FormBlock + public function GetFormBlockById(string $sId, string $sType): FormBlock { $sFilteredId = preg_replace('/[^0-9a-zA-Z_]/', '', $sId); if (strlen($sFilteredId) === 0) { throw new FormBlockException('Malformed name for block: '.json_encode($sId)); } - if (!$this->oCacheService->HasEntry(FormsCompiler::CACHE_POOL, $sFilteredId)) { - throw new FormBlockException('No block found for: '.json_encode($sFilteredId).' original value asked: '.json_encode($sId)); + if (!$this->oCacheService->HasEntry(self::CACHE_POOL, $sFilteredId) || utils::IsDevelopmentEnvironment()) { + // Cache not found, compile the form + $sPHPContent = FormsCompiler::GetInstance()->CompileForm($sFilteredId, $sType); + $this->oCacheService->StorePhpContent(FormBlockService::CACHE_POOL, $sId, "oCacheService->Fetch(FormsCompiler::CACHE_POOL, $sFilteredId); + $this->oCacheService->Fetch(self::CACHE_POOL, $sFilteredId); + $sFormBlockClass = 'FormFor__'.$sFilteredId; - return new $sFilteredId($sFilteredId); + return new $sFormBlockClass($sFilteredId); } } diff --git a/sources/Forms/Compiler/FormsCompiler.php b/sources/Forms/Compiler/FormsCompiler.php index 09d489bd3..b1c3540bf 100644 --- a/sources/Forms/Compiler/FormsCompiler.php +++ b/sources/Forms/Compiler/FormsCompiler.php @@ -9,15 +9,22 @@ namespace Combodo\iTop\Forms\Compiler; use Combodo\iTop\DesignDocument; use Combodo\iTop\Forms\Block\Base\FormBlock; +use Combodo\iTop\Forms\Block\FormBlockService; use Combodo\iTop\PropertyTree\PropertyTreeFactory; use Combodo\iTop\Service\Cache\DataModelDependantCache; use DOMFormatException; +use utils; +/** + * XML to PHP Forms compiler. + * + * @package Combodo\iTop\Forms\Compiler + * @since 3.3.0 + */ class FormsCompiler { private static FormsCompiler $oInstance; private DataModelDependantCache $oCacheService; - public const CACHE_POOL = 'Forms'; protected function __construct() { @@ -38,7 +45,7 @@ class FormsCompiler * * @param string $sXMLContent property tree structure in xml * - * @return string + * @return string Generated PHP * @throws \Combodo\iTop\Forms\Compiler\FormsCompilerException * @throws \Combodo\iTop\PropertyTree\PropertyTreeException * @throws \DOMFormatException @@ -60,8 +67,24 @@ class FormsCompiler return $oPropertyTree->ToPHPFormBlock(); } - public function StoreFormFromContent(string $sId, string $sPHPContent): void + /** + * @param string $sId + * @param string $sType + * + * @return string Generated PHP + * @throws \Combodo\iTop\Forms\Compiler\FormsCompilerException + * @throws \Combodo\iTop\PropertyTree\PropertyTreeException + * @throws \DOMFormatException + */ + public function CompileForm(string $sId, string $sType): string { - $this->oCacheService->StorePhpContent(self::CACHE_POOL, $sId, $sPHPContent); + $sPath = utils::GetAbsoluteModulePath('core')."property_trees/$sType/$sId.xml"; + if (!file_exists($sPath)) { + throw new FormsCompilerException("Properties definition $sType/$sId not present"); + } + + $sXMLContent = file_get_contents($sPath); + + return $this->CompileFormFromXML($sXMLContent); } } diff --git a/sources/Forms/Compiler/FormsController.php b/sources/Forms/Controller/FormsController.php similarity index 96% rename from sources/Forms/Compiler/FormsController.php rename to sources/Forms/Controller/FormsController.php index 7f1e3184c..8dba55099 100644 --- a/sources/Forms/Compiler/FormsController.php +++ b/sources/Forms/Controller/FormsController.php @@ -5,14 +5,13 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ -namespace Combodo\iTop\Forms\Compiler; +namespace Combodo\iTop\Forms\Controller; use Combodo\iTop\Application\TwigBase\Controller\Controller; use Combodo\iTop\Forms\Block\FormBlockService; use Combodo\iTop\ItopSdkFormDemonstrator\Helper\ItopSdkFormDemonstratorLog; use Exception; use IssueLog; -use utils; class FormsController extends Controller { @@ -32,7 +31,7 @@ class FormsController extends Controller $sDashletId = $oRequest->query->get('dashlet_code'); // Get the form block from the service (and the compiler) - $oFormBlock = FormBlockService::GetInstance()->GetFormBlockById($sDashletId); + $oFormBlock = FormBlockService::GetInstance()->GetFormBlockById($sDashletId, 'Dashlet'); $oBuilder = $this->GetFormBuilder($oFormBlock, []); $oForm = $oBuilder->getForm(); $oForm->handleRequest($oRequest); diff --git a/sources/PropertyTree/PropertyTreeDesign.php b/sources/PropertyTree/PropertyTreeDesign.php new file mode 100644 index 000000000..0c0576d36 --- /dev/null +++ b/sources/PropertyTree/PropertyTreeDesign.php @@ -0,0 +1,80 @@ +LoadFromCompiledDesigns($sDesignSourceId, $sType); + } + } + + /** + * Gets the data where the compiler has left them... + * @param $sDesignSourceId String Identifier of the section module_design (generally a module name) + * @throws Exception + */ + protected function LoadFromCompiledDesigns(string $sDesignSourceId, string $sType) + { + $sDesignDir = APPROOT.'env-'.utils::GetCurrentEnvironment()."/core/property_trees/$sType/"; + $sFile = $sDesignDir.$sDesignSourceId.'.xml'; + if (!file_exists($sFile)) { + $aFiles = glob($sDesignDir.'/*/*.xml'); + if (count($aFiles) == 0) { + $sAvailable = 'none!'; + } else { + $aAvailable = []; + foreach ($aFiles as $sFile) { + $aAvailable[] = "'".basename($sFile, '.xml')."'"; + } + $sAvailable = implode(', ', $aAvailable); + } + throw new Exception("Could not load property tree design '$sDesignSourceId'. Available designs: $sAvailable"); + } + + // Silently keep track of errors + libxml_use_internal_errors(true); + libxml_clear_errors(); + $this->load($sFile); + //$bValidated = $oDocument->schemaValidate(APPROOT.'setup/itop_design.xsd'); + $aErrors = libxml_get_errors(); + if (count($aErrors) > 0) { + $aDisplayErrors = []; + foreach ($aErrors as $oXmlError) { + $aDisplayErrors[] = 'Line '.$oXmlError->line.': '.$oXmlError->message; + } + + throw new Exception("Invalid XML in '$sFile'. Errors: ".implode(', ', $aDisplayErrors)); + } + } + + /** + * Overload of the standard API + * + * @param $filename + * @param int $options + * + * @return int + */ + // Return type union is not supported by PHP 7.4, we can remove the following PHP attribute and add the return type once iTop min PHP version is PHP 8.0+ + #[\ReturnTypeWillChange] + public function save($filename, $options = null) + { + $this->documentElement->setAttribute('xsi:noNamespaceSchemaLocation', 'https://www.combodo.com/itop-schema/'.ITOP_DESIGN_LATEST_VERSION); + + return parent::save($filename); + } +}