N°9065 - XML Definition for Dashlet properties

This commit is contained in:
Eric Espie
2026-01-06 15:59:46 +01:00
parent 904cd0b518
commit efb1bd765b
56 changed files with 2433 additions and 1270 deletions

View File

@@ -0,0 +1,133 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Compiler;
use Combodo\iTop\DesignDocument;
use Combodo\iTop\PropertyType\PropertyType;
use Combodo\iTop\PropertyType\PropertyTypeFactory;
use utils;
/**
* XML to PHP Forms compiler.
*
* @package Combodo\iTop\PropertyType\Compiler
* @since 3.3.0
*/
class PropertyTypeCompiler
{
private static PropertyTypeCompiler $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): PropertyTypeCompiler
{
if (!isset(static::$oInstance)) {
static::$oInstance = new PropertyTypeCompiler();
}
return static::$oInstance;
}
/**
* @param string $sXMLContent
*
* @return \Combodo\iTop\PropertyType\PropertyType
* @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
protected function CompilePropertyTypeFromXML(string $sXMLContent): PropertyType
{
$oDoc = new DesignDocument();
libxml_clear_errors();
$oDoc->loadXML($sXMLContent);
$aErrors = libxml_get_errors();
if (count($aErrors) > 0) {
throw new PropertyTypeCompilerException('Property types definition not correctly formatted!');
}
/** @var \Combodo\iTop\DesignElement $oRoot */
$oRoot = $oDoc->firstChild;
return PropertyTypeFactory::GetInstance()->CreatePropertyTypeFromDom($oRoot);
}
/**
* @param string $sId
* @param string $sType
*
* @return string
* @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException
*/
protected function GetXMLContent(string $sId, string $sType): string
{
$sPath = utils::GetAbsoluteModulePath('core')."property_types/$sType/$sId.xml";
if (!file_exists($sPath)) {
throw new PropertyTypeCompilerException("Properties definition $sType/$sId not present");
}
return file_get_contents($sPath);
}
/**
* Compile XML property tree into PHP to create the configuration form
*
* @param string $sXMLContent property tree structure in xml
*
* @return string Generated PHP
* @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function CompileFormFromXML(string $sXMLContent): string
{
$oPropertyType = $this->CompilePropertyTypeFromXML($sXMLContent);
return $oPropertyType->ToPHPFormBlock();
}
/**
* @param string $sXMLContent
*
* @return string
* @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function CompileEntityFromXML(string $sXMLContent): string
{
$oPropertyType = $this->CompilePropertyTypeFromXML($sXMLContent);
return $oPropertyType->ToPHPEntity();
}
/**
* @param string $sId
* @param string $sType
*
* @return string Generated PHP
* @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function CompileForm(string $sId, string $sType): string
{
$sXMLContent = $this->GetXMLContent($sId, $sType);
return $this->CompileFormFromXML($sXMLContent);
}
public function CompileEntity(string $sId, string $sType): string
{
$sXMLContent = $this->GetXMLContent($sId, $sType);
return $this->CompileEntityFromXML($sXMLContent);
}
}

View File

@@ -0,0 +1,14 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Compiler;
use Combodo\iTop\PropertyType\PropertyTypeException;
class PropertyTypeCompilerException extends PropertyTypeException
{
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
/**
* A property type is a definition of properties (organized in tree)
* used for dashlet for example.
* @since 3.3.0
*/
class PropertyType
{
private string $sParentType = '';
private string $sId;
private AbstractValueType $oValueType;
/**
* @param \Combodo\iTop\DesignElement $oDomNode
*
* @return void
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function InitFromDomNode(DesignElement $oDomNode): void
{
$this->sId = $oDomNode->getAttribute('id');
$this->sParentType = $oDomNode->GetChildText('extends', '');
$oDefinitionNode = $oDomNode->GetUniqueElement('definition');
$sDefinitionNodeType = $oDefinitionNode->getAttribute('xsi:type');
if (!is_a($sDefinitionNodeType, AbstractValueType::class, true)) {
throw new PropertyTypeException('Unsupported type '.json_encode($sDefinitionNodeType).' in ');
}
$this->oValueType = new $sDefinitionNodeType();
$this->oValueType->SetRootId($this->sId);
$this->oValueType->InitFromDomNode($oDefinitionNode);
}
public function ToPHPFormBlock(): string
{
$aPHPFragments = [];
if ($this->oValueType->IsLeaf()) {
$sLocalPHP = <<<PHP
class FormFor__$this->sId extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
PHP;
$sLocalPHP .= "\n".$this->oValueType->ToPHPFormBlock($aPHPFragments);
$sLocalPHP .= <<<PHP
}
}
PHP;
$aPHPFragments[] = $sLocalPHP;
} else {
$this->oValueType->ToPHPFormBlock($aPHPFragments);
}
return implode("\n\n", $aPHPFragments);
}
public function GetParentType(): string
{
return $this->sParentType;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType;
use Combodo\iTop\DesignDocument;
use Exception;
use ReturnTypeWillChange;
use utils;
/**
* Load and save XML definitions of property types
* @since 3.3.0
*/
class PropertyTypeDesign 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);
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType;
use Exception;
/**
* @since 3.3.0
*/
class PropertyTypeException extends Exception
{
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
/**
* Build a property type form XML DOM
* @since 3.3.0
*/
class PropertyTypeFactory
{
private static PropertyTypeFactory $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): PropertyTypeFactory
{
if (!isset(static::$oInstance)) {
static::$oInstance = new PropertyTypeFactory();
}
return static::$oInstance;
}
/**
* Create a property node from a design element
*
* @param \Combodo\iTop\DesignElement $oDomNode
* @param \Combodo\iTop\PropertyType\ValueType\AbstractValueType|null $oParent
*
* @return \Combodo\iTop\PropertyType\PropertyType
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function CreatePropertyTypeFromDom(DesignElement $oDomNode, ?AbstractValueType $oParent = null): PropertyType
{
$oNode = new PropertyType();
$oNode->InitFromDomNode($oDomNode);
return $oNode;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\FormBlockException;
use Combodo\iTop\Forms\Block\FormBlockService;
use Combodo\iTop\PropertyType\Compiler\PropertyTypeCompiler;
use Combodo\iTop\Service\Cache\DataModelDependantCache;
use utils;
class PropertyTypeService
{
public const FORM_CACHE_POOL = 'Forms';
private DataModelDependantCache $oCacheService;
private static PropertyTypeService $oInstance;
protected function __construct()
{
$this->oCacheService = DataModelDependantCache::GetInstance();
}
final public static function GetInstance(): PropertyTypeService
{
if (!isset(static::$oInstance)) {
static::$oInstance = new PropertyTypeService();
}
return static::$oInstance;
}
/**
* @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\PropertyType\Compiler\PropertyTypeCompilerException
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function GetFormBlockById(string $sId, string $sType): FormBlock
{
$sFilteredId = $this->SanitizeId($sId);
$sCacheKey = $this->GetCacheKey($sType, $sFilteredId);
if (!$this->oCacheService->HasEntry(self::FORM_CACHE_POOL, $sCacheKey) || utils::IsDevelopmentEnvironment()) {
// Cache not found, compile the form
$sPHPContent = PropertyTypeCompiler::GetInstance()->CompileForm($sFilteredId, $sType);
$this->oCacheService->StorePhpContent(self::FORM_CACHE_POOL, $sCacheKey, "<?php\n\n$sPHPContent");
}
$this->oCacheService->FetchPHP(self::FORM_CACHE_POOL, $sCacheKey);
$sFormBlockClass = 'FormFor__'.$sFilteredId;
return new $sFormBlockClass($sFilteredId);
}
/**
* @param string $sId
*
* @return string
* @throws \Combodo\iTop\Forms\Block\FormBlockException
*/
private function SanitizeId(string $sId): string
{
$sFilteredId = preg_replace('/[^0-9a-zA-Z_]/', '', $sId);
if (strlen($sFilteredId) === 0 || $sFilteredId !== $sId) {
throw new FormBlockException('Malformed name for block: '.json_encode($sId));
}
return $sFilteredId;
}
/**
* @param string $sType
* @param string $sFilteredId
*
* @return string
*/
private function GetCacheKey(string $sType, string $sFilteredId): string
{
return $sType.'/'.$sFilteredId;
}
}

View File

@@ -0,0 +1,231 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock;
use Combodo\iTop\Forms\Block\Expression\NumberExpressionFormBlock;
use Combodo\iTop\Forms\Block\Expression\StringExpressionFormBlock;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\IO\Format\NumberIOFormat;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\Forms\IO\FormInput;
use Combodo\iTop\PropertyType\PropertyTypeException;
use Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType;
use Combodo\iTop\PropertyType\ValueType\Branch\ValueTypePropertyTree;
use Exception;
use Expression;
use utils;
/**
* @since 3.3.0
*/
abstract class AbstractValueType
{
protected ?AbstractBranchValueType $oParent;
protected string $sIdWithPath;
/** @var FormInput[] */
protected array $aInputs = [];
protected array $aOutputs = [];
protected array $aInputValues = [];
protected array $aDynamicInputValues = [];
protected array $aFormBlockOptionsForPHP = [];
protected string $sId;
protected ?string $sRelevanceCondition = null;
protected ?string $sLabel;
abstract public function GetFormBlockClass(): string;
/**
* @param \Combodo\iTop\DesignElement $oDomNode
* @param \Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType|null $oParent Parent node (used for trees)
*
* @return void
* @throws \DOMFormatException
*/
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractBranchValueType $oParent = null): void
{
$this->oParent = $oParent;
// id can already be set for the definition root node
$this->sId = $this->sId ?? $oDomNode->getAttribute('id');
if (is_null($oParent)) {
$this->sIdWithPath = $this->sId;
} else {
$this->sIdWithPath = $oParent->sIdWithPath.'__'.$this->sId;
}
$this->sLabel = $oDomNode->GetChildText('label');
$this->sRelevanceCondition = $oDomNode->GetChildText('relevance-condition');
$sBlockNodeClass = $this->GetFormBlockClass();
$oBlockNode = new $sBlockNodeClass('foo');
foreach ($oBlockNode->GetInputs() as $oInput) {
$sInputName = $oInput->GetName();
$this->aInputs[$sInputName] = $oInput;
$sInputValue = $oDomNode->GetChildText($sInputName);
if (utils::IsNotNullOrEmptyString($sInputValue)) {
$this->aInputValues[$sInputName] = $sInputValue;
}
}
foreach ($oBlockNode->GetOutputs() as $oOutput) {
$this->aOutputs[] = $oOutput->GetName();
}
}
public function GetFormBlockOptions(): array
{
return $this->aFormBlockOptionsForPHP;
}
public function GetInputValues(): array
{
return $this->aInputValues;
}
public function GetInputType(string $sInputName): string
{
return $this->aInputs[$sInputName]->GetDataType();
}
public function GetDynamicInputValues(): array
{
return $this->aDynamicInputValues;
}
public function GetOutputs(): array
{
return $this->aOutputs;
}
public function SetRootId(string $sId): void
{
$this->sId = $sId;
$this->sIdWithPath = $sId;
}
abstract public function IsLeaf(): bool;
abstract public function ToPHPFormBlock(array &$aPHPFragments = []): string;
/**
* @param array $aPHPFragments
*
* @return string
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
*/
protected function GetLocalPHPForValueType(?string $sFormBlockClass = null): string
{
$sFormBlockClass = $sFormBlockClass ?? $this->GetFormBlockClass();
$sInputs = '';
$sPrerequisiteExpressions = '';
if (!is_null($this->sRelevanceCondition)) {
$this->GenerateInputs('visible', $this->sRelevanceCondition, $sPrerequisiteExpressions, $sInputs);
}
foreach ($this->GetInputValues() as $sInputName => $sValue) {
$this->GenerateInputs($sInputName, $sValue, $sPrerequisiteExpressions, $sInputs);
}
foreach ($this->GetDynamicInputValues() as $sInputName => $sValue) {
$this->GenerateInputs($sInputName, $sValue, $sPrerequisiteExpressions, $sInputs, true);
}
$sLabel = utils::QuoteForPHP($this->sLabel ?? '');
$aOptions = [
'label' => $sLabel,
];
$aOptions += $this->GetFormBlockOptions();
$sOptions = '';
foreach ($aOptions as $sOption => $sValue) {
$sOptions .= "\t\t\t".utils::QuoteForPHP($sOption)." => $sValue,\n";
}
return <<<PHP
\t\t{$sPrerequisiteExpressions}\$this->Add('$this->sId', '$sFormBlockClass', [
$sOptions\t\t]){$sInputs};\n
PHP;
}
private function GenerateInputs(string $sInputName, string $sValue, string &$sPrerequisiteExpressions, string &$sInputs, bool $bIsDynamic = false): void
{
if (preg_match('/^{{(?<node>\w+)\.(?<output>\w+)}}$/', $sValue, $aMatches) === 1) {
$sVerb = $bIsDynamic ? 'AddInputDependsOn' : 'InputDependsOn';
$sInputs .= "\n ->$sVerb('$sInputName', '{$aMatches['node']}', '{$aMatches['output']}')";
} elseif (preg_match('/^{{(?<expression>.*)}}$/', $sValue, $aMatches) === 1) {
$sExpression = $aMatches['expression'];
$sBindings = '';
try {
$oExpression = Expression::FromOQL($sExpression);
} catch (Exception $e) {
throw new PropertyTypeException("Node: {$this->sId}, invalid syntax in condition: ".$e->getMessage());
}
$aFieldsToResolve = array_unique($oExpression->ListRequiredFields());
foreach ($aFieldsToResolve as $sFieldToResolve) {
if (preg_match('/(?<node>\w+)\.(?<output>\w+)/', $sFieldToResolve, $aMatches) === 1) {
$sNode = $aMatches['node'];
$oSibling = $this->GetSibling($sNode);
if (is_null($oSibling)) {
// Search in collection
if (is_a($this->oParent ?? null, 'Combodo-ValueType-Collection')) {
$bSourceNodeFound = false;
$aSiblings = $this->oParent->GetChildren();
foreach ($aSiblings as $oSibling) {
if ($oSibling->sId == $sNode) {
$bSourceNodeFound = true;
break;
}
}
if (!$bSourceNodeFound) {
throw new PropertyTypeException("node: {$this->sId}, source: $sNode not found in collection: {$this->oParent->sId}");
}
} else {
throw new PropertyTypeException("Node: {$this->sId}, invalid source in condition: $sNode");
}
}
$sOutput = $aMatches['output'];
if (!in_array($sOutput, $oSibling->GetOutputs())) {
throw new PropertyTypeException("Node: {$this->sId}, invalid output in condition: $sFieldToResolve");
}
$sBindings .= "\n\t\t\t->AddInputDependsOn('{$sNode}.$sOutput', '$sNode', '$sOutput')";
} else {
throw new PropertyTypeException("Node: {$this->sId}, missing output or source in condition: $sFieldToResolve");
}
}
$sExpressionClass = match ($this->GetInputType($sInputName)) {
BooleanIOFormat::class => BooleanExpressionFormBlock::class,
StringIOFormat::class, ClassIOFormat::class => StringExpressionFormBlock::class,
NumberIOFormat::class => NumberExpressionFormBlock::class,
default => throw new PropertyTypeException("Node: {$this->sId}, unsupported expression for input type: $sInputName"),
};
$sExpression = utils::QuoteForPHP($sExpression);
$sPrerequisiteExpressions = <<<PHP
\$this->Add('{$this->sId}_{$sInputName}_expression', '$sExpressionClass', [
'expression' => $sExpression,
]){$sBindings};\n\n\t\t
PHP;
$sVerb = $bIsDynamic ? 'AddInputDependsOn' : 'InputDependsOn';
$sInputs .= "\n\t\t\t->$sVerb('$sInputName', '{$this->sId}_{$sInputName}_expression', 'result')";
} else {
$sInputs .= "\n\t\t\t->SetInputValue('$sInputName', ".utils::QuoteForPHP($sValue).')';
}
}
protected function GetSibling(string $sId): ?AbstractValueType
{
if (is_null($this->oParent)) {
return null;
}
return $this->oParent->GetChild($sId);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Branch;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
/**
* @since 3.3.0
*/
abstract class AbstractBranchValueType extends AbstractValueType
{
/** @var AbstractValueType[] */
protected array $aChildren = [];
/**
* @return bool
*/
public function IsLeaf(): bool
{
return false;
}
/**
* @return bool
*/
public function IsRoot(): bool
{
return is_null($this->oParent);
}
/**
* @param \Combodo\iTop\PropertyType\ValueType\AbstractValueType $oChild
*
* @return void
*/
public function AddChild(AbstractValueType $oChild): void
{
$this->aChildren[$oChild->sId] = $oChild;
}
/**
* @param string $sId
*
* @return \Combodo\iTop\PropertyType\ValueType\AbstractValueType|null
*/
public function GetChild(string $sId): ?AbstractValueType
{
return $this->aChildren[$sId] ?? null;
}
/**
* @return AbstractValueType[]
*/
public function GetChildren(): array
{
return $this->aChildren;
}
}

View File

@@ -0,0 +1,77 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Branch;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Forms\Block\Base\CollectionBlock;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
use Combodo\iTop\PropertyType\ValueType\ValueTypeFactory;
use utils;
/**
* @since 3.3.0
*/
class ValueTypeCollection extends ValueTypePropertyTree
{
/**
* @param \Combodo\iTop\DesignElement $oDomNode
* @param \Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType|null $oParent
*
* @return void
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractBranchValueType $oParent = null): void
{
parent::InitFromDomNode($oDomNode, $oParent);
$this->aFormBlockOptionsForPHP['button_label'] = utils::QuoteForPHP('UI:AddSubTree');
$this->sSubTreeClass = 'SubFormFor__'.$this->sIdWithPath;
$this->aFormBlockOptionsForPHP['block_entry_type'] = utils::QuoteForPHP($this->sSubTreeClass);
// read child properties
foreach ($oDomNode->GetUniqueElement('prototype')->childNodes as $oNode) {
if ($oNode instanceof DesignElement) {
$this->AddChild(ValueTypeFactory::GetInstance()->CreateValueTypeFromDomNode($oNode, $this));
}
}
}
public function GetFormBlockClass(): string
{
return CollectionBlock::class;
}
/**
* @param array $aPHPFragments
*
* @return string
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
*/
public function ToPHPFormBlock(array &$aPHPFragments = []): string
{
$sSubClassPHP = <<<PHP
class $this->sSubTreeClass extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
PHP;
foreach ($this->GetChildren() as $oProperty) {
$sSubClassPHP .= "\n".$oProperty->ToPHPFormBlock($aPHPFragments);
}
$sSubClassPHP .= <<<PHP
}
}
PHP;
$aPHPFragments[] = $sSubClassPHP;
return $this->GetLocalPHPForValueType();
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Branch;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock;
use Combodo\iTop\Forms\Block\Expression\NumberExpressionFormBlock;
use Combodo\iTop\Forms\Block\Expression\StringExpressionFormBlock;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\IO\Format\NumberIOFormat;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\PropertyType\PropertyTypeException;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
use Combodo\iTop\PropertyType\ValueType\ValueTypeFactory;
use Exception;
use Expression;
use utils;
/**
* @since 3.3.0
*/
class ValueTypePropertyTree extends AbstractBranchValueType
{
protected string $sSubTreeClass;
public function GetFormBlockClass(): string
{
return FormBlock::class;
}
/**
* @param \Combodo\iTop\DesignElement $oDomNode
* @param \Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType|null $oParent
*
* @return void
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractBranchValueType $oParent = null): void
{
parent::InitFromDomNode($oDomNode, $oParent);
// read child properties
$oNodes = $oDomNode->GetOptionalElement('nodes');
if (!is_null($oNodes)) {
foreach ($oNodes->childNodes as $oNode) {
if ($oNode instanceof DesignElement) {
$this->AddChild(ValueTypeFactory::GetInstance()->CreateValueTypeFromDomNode($oNode, $this));
}
}
}
}
public function ToPHPFormBlock(array &$aPHPFragments = []): string
{
if ($this->IsRoot()) {
$this->sSubTreeClass = 'FormFor__'.$this->sId;
} else {
$this->sSubTreeClass = 'SubFormFor__'.$this->sIdWithPath;
}
$sLocalPHP = <<<PHP
class $this->sSubTreeClass extends Combodo\iTop\Forms\Block\Base\FormBlock
{
protected function BuildForm(): void
{
PHP;
foreach ($this->aChildren as $oChild) {
$sLocalPHP .= "\n".$oChild->ToPHPFormBlock($aPHPFragments);
}
$sLocalPHP .= <<<PHP
}
}
PHP;
$aPHPFragments[] = $sLocalPHP;
return $this->GetLocalPHPForValueType($this->sSubTreeClass);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock;
use Combodo\iTop\Forms\Block\Expression\NumberExpressionFormBlock;
use Combodo\iTop\Forms\Block\Expression\StringExpressionFormBlock;
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
use Combodo\iTop\Forms\IO\Format\ClassIOFormat;
use Combodo\iTop\Forms\IO\Format\NumberIOFormat;
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
use Combodo\iTop\PropertyType\PropertyTypeException;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
use Exception;
use Expression;
use utils;
/**
* @since 3.3.0
*/
abstract class AbstractLeafValueType extends AbstractValueType
{
public function IsLeaf(): bool
{
return true;
}
/**
* @param array $aPHPFragments
*
* @return string
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
*/
public function ToPHPFormBlock(array &$aPHPFragments = []): string
{
return $this->GetLocalPHPForValueType();
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\DataModel\Dashlet\AggregateFunctionFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeAggregateFunction extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return AggregateFunctionFormBlock::class;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\Base\CheckboxFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeBoolean extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return CheckboxFormBlock::class;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType;
use Combodo\iTop\PropertyType\ValueType\Branch\ValueTypePropertyTree;
use utils;
/**
* @since 3.3.0
*/
class ValueTypeChoice extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return ChoiceFormBlock::class;
}
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractBranchValueType $oParent = null): void
{
parent::InitFromDomNode($oDomNode, $oParent);
$sChoices = "[\n";
foreach ($oDomNode->GetNodes('values/value') as $oValueNode) {
/** @var DesignElement $oValueNode */
$sValue = utils::QuoteForPHP($oValueNode->GetAttribute('id'));
$sLabel = utils::QuoteForPHP($oValueNode->GetChildText('label'));
$sChoices .= <<<PHP
\Dict::S($sLabel) => $sValue,
PHP;
}
$sChoices .= "\t\t\t]";
$this->aFormBlockOptionsForPHP['choices'] = $sChoices;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Forms\Block\Base\ChoiceFromInputsBlock;
use Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType;
use Combodo\iTop\PropertyType\ValueType\Branch\ValueTypePropertyTree;
use utils;
/**
* @since 3.3.0
*/
class ValueTypeChoiceFromInput extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return ChoiceFromInputsBlock::class;
}
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractBranchValueType $oParent = null): void
{
parent::InitFromDomNode($oDomNode, $oParent);
foreach ($oDomNode->GetNodes('values/value') as $oValueNode) {
/** @var DesignElement $oValueNode */
$sValue = $oValueNode->GetAttribute('id');
$sLabel = $oValueNode->GetChildText('label');
$this->aDynamicInputValues[$sValue] = $sLabel;
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType;
use Combodo\iTop\PropertyType\ValueType\Branch\ValueTypePropertyTree;
use Combodo\iTop\Service\DependencyInjection\ServiceLocator;
use utils;
/**
* @since 3.3.0
*/
class ValueTypeClass extends AbstractLeafValueType
{
protected array $aCategories = [];
public function GetFormBlockClass(): string
{
return ChoiceFormBlock::class;
}
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractBranchValueType $oParent = null): void
{
parent::InitFromDomNode($oDomNode, $oParent);
$sCategories = $oDomNode->GetChildText('categories-csv');
/** @var \ModelReflection $oModelReflection */
$oModelReflection = ServiceLocator::GetInstance()->get('ModelReflection');
$sChoices = "[\n";
$aClasses = $oModelReflection->GetClasses($sCategories, true);
sort($aClasses);
foreach ($aClasses as $sClass) {
if ($oModelReflection->IsAbstract($sClass)) {
continue;
}
$sValue = utils::QuoteForPHP($sClass);
$sChoices .= <<<PHP
\Dict::S('Class:$sClass') => $sValue,
PHP;
}
$sChoices .= "\t\t\t]";
$this->aFormBlockOptionsForPHP['choices'] = $sChoices;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\DataModel\AttributeChoiceFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeClassAttribute extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return AttributeChoiceFormBlock::class;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\DataModel\Dashlet\ClassAttributeGroupByFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeClassAttributeGroupBy extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return ClassAttributeGroupByFormBlock::class;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\DataModel\AttributeValueChoiceFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeClassAttributeValue extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return AttributeValueChoiceFormBlock::class;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\DesignElement;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
use Combodo\iTop\Forms\Block\Base\FormBlock;
use Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType;
use Combodo\iTop\PropertyType\ValueType\Leaf\AbstractLeafValueType;
use Combodo\iTop\PropertyType\ValueType\ValueTypeFactory;
class ValueTypeCollectionOfValues extends AbstractLeafValueType
{
private string $sFormBlockClass;
public function GetFormBlockClass(): string
{
return $this->sFormBlockClass;
}
public function InitFromDomNode(DesignElement $oDomNode, ?AbstractBranchValueType $oParent = null): void
{
$oNode = $oDomNode->GetUniqueElement('value-type');
$oRealValueType = ValueTypeFactory::GetInstance()->CreateValueTypeFromDomNode($oNode, $oParent);
$this->sFormBlockClass = $oRealValueType->getFormBlockClass();
if (is_a($this->sFormBlockClass, ChoiceFormBlock::class, true)) {
$this->aFormBlockOptionsForPHP['multiple'] = 'true';
}
parent::InitFromDomNode($oDomNode, $oParent);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeIcon extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return ChoiceFormBlock::class;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\Base\IntegerFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeInteger extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return IntegerFormBlock::class;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\DataModel\LabelFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeLabel extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return LabelFormBlock::class;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\DataModel\OqlFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeOQL extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return OqlFormBlock::class;
}
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
/**
* @since 3.3.0
*/
class ValueTypeProfileName extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return '';
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\Base\TextFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeString extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return TextFormBlock::class;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType\Leaf;
use Combodo\iTop\Forms\Block\Base\TextAreaFormBlock;
/**
* @since 3.3.0
*/
class ValueTypeText extends AbstractLeafValueType
{
public function GetFormBlockClass(): string
{
return TextAreaFormBlock::class;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\ValueType;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\PropertyTypeException;
use utils;
/**
* Build Value type from XML DOM
* @since 3.3.0
*/
class ValueTypeFactory
{
private static ValueTypeFactory $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): ValueTypeFactory
{
if (!isset(static::$oInstance)) {
static::$oInstance = new ValueTypeFactory();
}
return static::$oInstance;
}
/**
* @param \Combodo\iTop\DesignElement $oDomNode
* @param \Combodo\iTop\PropertyType\ValueType\AbstractValueType|null $oParent
*
* @return \Combodo\iTop\PropertyType\ValueType\AbstractValueType
* @throws \Combodo\iTop\PropertyType\PropertyTypeException
* @throws \DOMFormatException
*/
public function CreateValueTypeFromDomNode(DesignElement $oDomNode, ?AbstractValueType $oParent = null): AbstractValueType
{
$sNodeType = $oDomNode->getAttribute('xsi:type');
if (utils::IsNullOrEmptyString($sNodeType)) {
$sId = $oDomNode->getAttribute('id');
throw new PropertyTypeException("Node: $sId, missing value-type in node specification");
}
if (is_a($sNodeType, AbstractValueType::class, true)) {
$oNode = new $sNodeType();
$oNode->InitFromDomNode($oDomNode, $oParent);
return $oNode;
}
$sId = $oDomNode->getAttribute('id');
throw new PropertyTypeException("Node: $sId, unknown type node class: ".json_encode($sNodeType));
}
}