N°8772 - PropertyTrees definition in DataModel

This commit is contained in:
Eric Espie
2025-12-15 16:19:22 +01:00
parent eff59a93fd
commit a77e54c8cb
9 changed files with 250 additions and 17 deletions

View File

@@ -1185,5 +1185,106 @@
</property>
</properties>
</attribute_properties_definition>
<property_trees _delta="define" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://www.combodo.com/itop-schema/3.3">
<property_tree id="DashletGroupBy" xsi:type="Combodo-PropertyTree">
<extends>Dashlet</extends>
<nodes>
<node id="title" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-Title</label>
<value-type xsi:type="Combodo-ValueTypeLabel"/><!-- LabelFormBlock -->
</node>
<node id="query" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-Query</label>
<data-transform>
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
</data-transform>
<value-type xsi:type="Combodo-ValueTypeOQL"/><!-- OqlFormBlock -->
</node>
<node id="group_by" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-GroupBy</label>
<data-transform>
<xml-node-empty-not-allowed/>
</data-transform>
<value-type xsi:type="Combodo-ValueTypeClassAttributeGroupBy"> <!-- par défaut, il proposera le state attribute de la classe -->
<class>{{query.selected_class}}</class>
</value-type>
</node>
<node id="style" xsi:type="Combodo-Property"> <!-- possible de le cacher, etc.... celui-ci nous met dedans -->
<label>UI:DashletGroupBy:Prop-Style</label>
<data-transform>
<xml-node-empty-defaults-to>bars</xml-node-empty-defaults-to>
</data-transform>
<value-type xsi:type="Combodo-ValueTypeChoice">
<values>
<value id="bars">
<label>UI:DashletGroupByBars:Label</label>
</value>
<value id="pie">
<label>UI:DashletGroupByPie:Label</label>
</value>
<value id="table">
<label>UI:DashletGroupByTable:Label</label>
</value>
</values>
</value-type>
</node>
<node id="aggregation_function" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-Function</label>
<data-transform>
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
</data-transform>
<value-type xsi:type="Combodo-ValueTypeAggregateFunction"> <!-- par défaut : count -->
<class>{{query.selected_class}}</class> <!-- pour savoir si il y a des attributs additionnables -->
</value-type>
</node>
<node id="aggregation_attribute" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-FunctionAttribute</label>
<relevance-condition>{{aggregation_function.value != 'count'}}</relevance-condition>
<data-transform>
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
</data-transform>
<value-type xsi:type="Combodo-ValueTypeClassAttribute">
<class>{{query.selected_class}}</class>
<category>numeric</category>
</value-type>
</node>
<node id="order_by" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-OrderField</label>
<value-type xsi:type="Combodo-ValueTypeChoice">
<values>
<value id="attribute">
<label>{{aggregation_attribute.label}}</label>
</value>
<value id="function">
<label>{{aggregation_function.label}}</label>
</value>
</values>
</value-type>
</node>
<node id="limit" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-Limit</label>
<relevance-condition>{{order_by.value = 'function'}}</relevance-condition>
<value-type xsi:type="Combodo-ValueTypeInteger"/>
</node>
<node id="order_direction" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-OrderDirection</label>
<data-transform>
<xml-node-empty-not-allowed/>
</data-transform>
<value-type xsi:type="Combodo-ValueTypeChoice">
<values>
<value id="asc">
<label>UI:DashletGroupBy:Order:asc</label>
</value>
<value id="desc">
<label>UI:DashletGroupBy:Order:desc</label>
</value>
</values>
</value-type>
<display-default>desc</display-default>
</node>
</nodes>
</property_tree>
</property_trees>
</meta>
</itop_design>

View File

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

View File

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

View File

@@ -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
*/

View File

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

View File

@@ -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, "<?php\n\n$sPHPContent");
}
$this->oCacheService->Fetch(FormsCompiler::CACHE_POOL, $sFilteredId);
$this->oCacheService->Fetch(self::CACHE_POOL, $sFilteredId);
$sFormBlockClass = 'FormFor__'.$sFilteredId;
return new $sFilteredId($sFilteredId);
return new $sFormBlockClass($sFilteredId);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,80 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyTree;
use Combodo\iTop\DesignDocument;
use Exception;
use utils;
class PropertyTreeDesign extends DesignDocument
{
public function __construct(string $sDesignSourceId = null, string $sType = 'Default')
{
parent::__construct();
if (!is_null($sDesignSourceId)) {
$this->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);
}
}