Save as XML serialized

This commit is contained in:
Eric Espie
2026-01-21 17:27:16 +01:00
parent 945d2f32c5
commit e909c41445
27 changed files with 536 additions and 919 deletions

View File

@@ -146,7 +146,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
if ($oDashlet::IsVisible()) {
$sDashletId = $oDashlet->GetID();
$sDashletClass = $oDashlet->GetDashletType();
$aDashletDenormalizedProperties = $oDashlet->GetDenormalizedProperties();
$aDashletDenormalizedProperties = $oDashlet->GetModelData();
$aDashletsInfo = DashletService::GetInstance()->GetDashletDefinition($sDashletClass);
// GRID LAYOUT: Set position relative to grid

View File

@@ -453,7 +453,7 @@
//
// Method: jQuery.deparam
//
// Deserialize a params string into an object, optionally coercing numbers,
// DeserializeFromDOMNode a params string into an object, optionally coercing numbers,
// booleans, null and undefined values; this method is the counterpart to the
// internal jQuery.param method.
//

View File

@@ -587,14 +587,12 @@ return array(
'Combodo\\iTop\\PropertyType\\PropertyTypeFactory' => $baseDir . '/sources/PropertyType/PropertyTypeFactory.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeService' => $baseDir . '/sources/PropertyType/PropertyTypeService.php',
'Combodo\\iTop\\PropertyType\\Serializer\\SerializerException' => $baseDir . '/sources/PropertyType/Serializer/SerializerException.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLEncoder' => $baseDir . '/sources/PropertyType/Serializer/XMLEncoder.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\AbstractXMLFormat' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/AbstractXMLFormat.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatCSV' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatCSV.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatCollectionWithId' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatCollectionWithId.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFactory' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFlatArray' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatValueAsId' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLNormalizer' => $baseDir . '/sources/PropertyType/Serializer/XMLNormalizer.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLSerializer' => $baseDir . '/sources/PropertyType/Serializer/XMLSerializer.php',
'Combodo\\iTop\\PropertyType\\ValueType\\AbstractValueType' => $baseDir . '/sources/PropertyType/ValueType/AbstractValueType.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\AbstractBranchValueType' => $baseDir . '/sources/PropertyType/ValueType/Branch/AbstractBranchValueType.php',
@@ -3962,5 +3960,5 @@ return array(
'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php',
'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php',
'utils' => $baseDir . '/application/utils.inc.php',
'©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
'<EFBFBD>' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
);

View File

@@ -973,14 +973,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\PropertyType\\PropertyTypeFactory' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeFactory.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeService' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeService.php',
'Combodo\\iTop\\PropertyType\\Serializer\\SerializerException' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/SerializerException.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLEncoder' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLEncoder.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\AbstractXMLFormat' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/AbstractXMLFormat.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatCSV' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatCSV.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatCollectionWithId' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatCollectionWithId.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFactory' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFlatArray' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatValueAsId' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLNormalizer' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLNormalizer.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLSerializer' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLSerializer.php',
'Combodo\\iTop\\PropertyType\\ValueType\\AbstractValueType' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/AbstractValueType.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\AbstractBranchValueType' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Branch/AbstractBranchValueType.php',
@@ -4348,7 +4346,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
'utils' => __DIR__ . '/../..' . '/application/utils.inc.php',
'©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
'<EFBFBD>' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
);
public static function getInitializer(ClassLoader $loader)

View File

@@ -28,341 +28,341 @@ use Symfony\Contracts\Cache\CacheInterface;
*/
class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
use LoggerAwareTrait;
use LoggerAwareTrait;
private bool $storeSerialized;
private array $values = [];
private array $tags = [];
private array $expiries = [];
private int $defaultLifetime;
private float $maxLifetime;
private int $maxItems;
private bool $storeSerialized;
private array $values = [];
private array $tags = [];
private array $expiries = [];
private int $defaultLifetime;
private float $maxLifetime;
private int $maxItems;
private static \Closure $createCacheItem;
private static \Closure $createCacheItem;
/**
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true, float $maxLifetime = 0, int $maxItems = 0)
{
if (0 > $maxLifetime) {
throw new InvalidArgumentException(\sprintf('Argument $maxLifetime must be positive, %F passed.', $maxLifetime));
}
/**
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true, float $maxLifetime = 0, int $maxItems = 0)
{
if (0 > $maxLifetime) {
throw new InvalidArgumentException(\sprintf('Argument $maxLifetime must be positive, %F passed.', $maxLifetime));
}
if (0 > $maxItems) {
throw new InvalidArgumentException(\sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems));
}
if (0 > $maxItems) {
throw new InvalidArgumentException(\sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems));
}
$this->defaultLifetime = $defaultLifetime;
$this->storeSerialized = $storeSerialized;
$this->maxLifetime = $maxLifetime;
$this->maxItems = $maxItems;
self::$createCacheItem ??= \Closure::bind(
static function ($key, $value, $isHit, $tags) {
$item = new CacheItem();
$item->key = $key;
$item->value = $value;
$item->isHit = $isHit;
if (null !== $tags) {
$item->metadata[CacheItem::METADATA_TAGS] = $tags;
}
$this->defaultLifetime = $defaultLifetime;
$this->storeSerialized = $storeSerialized;
$this->maxLifetime = $maxLifetime;
$this->maxItems = $maxItems;
self::$createCacheItem ??= \Closure::bind(
static function ($key, $value, $isHit, $tags) {
$item = new CacheItem();
$item->key = $key;
$item->value = $value;
$item->isHit = $isHit;
if (null !== $tags) {
$item->metadata[CacheItem::METADATA_TAGS] = $tags;
}
return $item;
},
null,
CacheItem::class
);
}
return $item;
},
null,
CacheItem::class
);
}
public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed
{
$item = $this->getItem($key);
$metadata = $item->getMetadata();
public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed
{
$item = $this->getItem($key);
$metadata = $item->getMetadata();
// ArrayAdapter works in memory, we don't care about stampede protection
if (\INF === $beta || !$item->isHit()) {
$save = true;
$item->set($callback($item, $save));
if ($save) {
$this->save($item);
}
}
// ArrayAdapter works in memory, we don't care about stampede protection
if (\INF === $beta || !$item->isHit()) {
$save = true;
$item->set($callback($item, $save));
if ($save) {
$this->save($item);
}
}
return $item->get();
}
return $item->get();
}
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
public function hasItem(mixed $key): bool
{
if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
if ($this->maxItems) {
// Move the item last in the storage
$value = $this->values[$key];
unset($this->values[$key]);
$this->values[$key] = $value;
}
public function hasItem(mixed $key): bool
{
if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
if ($this->maxItems) {
// Move the item last in the storage
$value = $this->values[$key];
unset($this->values[$key]);
$this->values[$key] = $value;
}
return true;
}
\assert('' !== CacheItem::validateKey($key));
return true;
}
\assert('' !== CacheItem::validateKey($key));
return isset($this->expiries[$key]) && !$this->deleteItem($key);
}
return isset($this->expiries[$key]) && !$this->deleteItem($key);
}
public function getItem(mixed $key): CacheItem
{
if (!$isHit = $this->hasItem($key)) {
$value = null;
public function getItem(mixed $key): CacheItem
{
if (!$isHit = $this->hasItem($key)) {
$value = null;
if (!$this->maxItems) {
// Track misses in non-LRU mode only
$this->values[$key] = null;
}
} else {
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
}
if (!$this->maxItems) {
// Track misses in non-LRU mode only
$this->values[$key] = null;
}
} else {
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
}
return (self::$createCacheItem)($key, $value, $isHit, $this->tags[$key] ?? null);
}
return (self::$createCacheItem)($key, $value, $isHit, $this->tags[$key] ?? null);
}
public function getItems(array $keys = []): iterable
{
\assert(self::validateKeys($keys));
public function getItems(array $keys = []): iterable
{
\assert(self::validateKeys($keys));
return $this->generateItems($keys, microtime(true), self::$createCacheItem);
}
return $this->generateItems($keys, microtime(true), self::$createCacheItem);
}
public function deleteItem(mixed $key): bool
{
\assert('' !== CacheItem::validateKey($key));
unset($this->values[$key], $this->tags[$key], $this->expiries[$key]);
public function deleteItem(mixed $key): bool
{
\assert('' !== CacheItem::validateKey($key));
unset($this->values[$key], $this->tags[$key], $this->expiries[$key]);
return true;
}
return true;
}
public function deleteItems(array $keys): bool
{
foreach ($keys as $key) {
$this->deleteItem($key);
}
public function deleteItems(array $keys): bool
{
foreach ($keys as $key) {
$this->deleteItem($key);
}
return true;
}
return true;
}
public function save(CacheItemInterface $item): bool
{
if (!$item instanceof CacheItem) {
return false;
}
$item = (array) $item;
$key = $item["\0*\0key"];
$value = $item["\0*\0value"];
$expiry = $item["\0*\0expiry"];
public function save(CacheItemInterface $item): bool
{
if (!$item instanceof CacheItem) {
return false;
}
$item = (array) $item;
$key = $item["\0*\0key"];
$value = $item["\0*\0value"];
$expiry = $item["\0*\0expiry"];
$now = microtime(true);
$now = microtime(true);
if (null !== $expiry) {
if (!$expiry) {
$expiry = \PHP_INT_MAX;
} elseif ($expiry <= $now) {
$this->deleteItem($key);
if (null !== $expiry) {
if (!$expiry) {
$expiry = \PHP_INT_MAX;
} elseif ($expiry <= $now) {
$this->deleteItem($key);
return true;
}
}
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
return false;
}
if (null === $expiry && 0 < $this->defaultLifetime) {
$expiry = $this->defaultLifetime;
$expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry);
} elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) {
$expiry = $now + $this->maxLifetime;
}
return true;
}
}
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
return false;
}
if (null === $expiry && 0 < $this->defaultLifetime) {
$expiry = $this->defaultLifetime;
$expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry);
} elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) {
$expiry = $now + $this->maxLifetime;
}
if ($this->maxItems) {
unset($this->values[$key], $this->tags[$key]);
if ($this->maxItems) {
unset($this->values[$key], $this->tags[$key]);
// Iterate items and vacuum expired ones while we are at it
foreach ($this->values as $k => $v) {
if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) {
break;
}
// Iterate items and vacuum expired ones while we are at it
foreach ($this->values as $k => $v) {
if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) {
break;
}
unset($this->values[$k], $this->tags[$k], $this->expiries[$k]);
}
}
unset($this->values[$k], $this->tags[$k], $this->expiries[$k]);
}
}
$this->values[$key] = $value;
$this->expiries[$key] = $expiry ?? \PHP_INT_MAX;
$this->values[$key] = $value;
$this->expiries[$key] = $expiry ?? \PHP_INT_MAX;
if (null === $this->tags[$key] = $item["\0*\0newMetadata"][CacheItem::METADATA_TAGS] ?? null) {
unset($this->tags[$key]);
}
if (null === $this->tags[$key] = $item["\0*\0newMetadata"][CacheItem::METADATA_TAGS] ?? null) {
unset($this->tags[$key]);
}
return true;
}
return true;
}
public function saveDeferred(CacheItemInterface $item): bool
{
return $this->save($item);
}
public function saveDeferred(CacheItemInterface $item): bool
{
return $this->save($item);
}
public function commit(): bool
{
return true;
}
public function commit(): bool
{
return true;
}
public function clear(string $prefix = ''): bool
{
if ('' !== $prefix) {
$now = microtime(true);
public function clear(string $prefix = ''): bool
{
if ('' !== $prefix) {
$now = microtime(true);
foreach ($this->values as $key => $value) {
if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || str_starts_with($key, $prefix)) {
unset($this->values[$key], $this->tags[$key], $this->expiries[$key]);
}
}
foreach ($this->values as $key => $value) {
if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || str_starts_with($key, $prefix)) {
unset($this->values[$key], $this->tags[$key], $this->expiries[$key]);
}
}
if ($this->values) {
return true;
}
}
if ($this->values) {
return true;
}
}
$this->values = $this->tags = $this->expiries = [];
$this->values = $this->tags = $this->expiries = [];
return true;
}
return true;
}
/**
* Returns all cached values, with cache miss as null.
*/
public function getValues(): array
{
if (!$this->storeSerialized) {
return $this->values;
}
/**
* Returns all cached values, with cache miss as null.
*/
public function getValues(): array
{
if (!$this->storeSerialized) {
return $this->values;
}
$values = $this->values;
foreach ($values as $k => $v) {
if (null === $v || 'N;' === $v) {
continue;
}
if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
$values[$k] = serialize($v);
}
}
$values = $this->values;
foreach ($values as $k => $v) {
if (null === $v || 'N;' === $v) {
continue;
}
if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
$values[$k] = serialize($v);
}
}
return $values;
}
return $values;
}
/**
* @return void
*/
public function reset()
{
$this->clear();
}
/**
* @return void
*/
public function reset()
{
$this->clear();
}
private function generateItems(array $keys, float $now, \Closure $f): \Generator
{
foreach ($keys as $i => $key) {
if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
$value = null;
private function generateItems(array $keys, float $now, \Closure $f): \Generator
{
foreach ($keys as $i => $key) {
if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
$value = null;
if (!$this->maxItems) {
// Track misses in non-LRU mode only
$this->values[$key] = null;
}
} else {
if ($this->maxItems) {
// Move the item last in the storage
$value = $this->values[$key];
unset($this->values[$key]);
$this->values[$key] = $value;
}
if (!$this->maxItems) {
// Track misses in non-LRU mode only
$this->values[$key] = null;
}
} else {
if ($this->maxItems) {
// Move the item last in the storage
$value = $this->values[$key];
unset($this->values[$key]);
$this->values[$key] = $value;
}
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
}
unset($keys[$i]);
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
}
unset($keys[$i]);
yield $key => $f($key, $value, $isHit, $this->tags[$key] ?? null);
}
yield $key => $f($key, $value, $isHit, $this->tags[$key] ?? null);
}
foreach ($keys as $key) {
yield $key => $f($key, null, false);
}
}
foreach ($keys as $key) {
yield $key => $f($key, null, false);
}
}
private function freeze($value, string $key): string|int|float|bool|array|\UnitEnum|null
{
if (null === $value) {
return 'N;';
}
if (\is_string($value)) {
// Serialize strings if they could be confused with serialized objects or arrays
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
return serialize($value);
}
} elseif (!\is_scalar($value)) {
try {
$serialized = serialize($value);
} catch (\Exception $e) {
if (!isset($this->expiries[$key])) {
unset($this->values[$key]);
}
$type = get_debug_type($value);
$message = \sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
private function freeze($value, string $key): string|int|float|bool|array|\UnitEnum|null
{
if (null === $value) {
return 'N;';
}
if (\is_string($value)) {
// SerializeToDOMNode strings if they could be confused with serialized objects or arrays
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
return serialize($value);
}
} elseif (!\is_scalar($value)) {
try {
$serialized = serialize($value);
} catch (\Exception $e) {
if (!isset($this->expiries[$key])) {
unset($this->values[$key]);
}
$type = get_debug_type($value);
$message = \sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
return null;
}
// Keep value serialized if it contains any objects or any internal references
if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
return $serialized;
}
}
return null;
}
// Keep value serialized if it contains any objects or any internal references
if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
return $serialized;
}
}
return $value;
}
return $value;
}
private function unfreeze(string $key, bool &$isHit): mixed
{
if ('N;' === $value = $this->values[$key]) {
return null;
}
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
$value = unserialize($value);
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
$value = false;
}
if (false === $value) {
$value = null;
$isHit = false;
private function unfreeze(string $key, bool &$isHit): mixed
{
if ('N;' === $value = $this->values[$key]) {
return null;
}
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
$value = unserialize($value);
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
$value = false;
}
if (false === $value) {
$value = null;
$isHit = false;
if (!$this->maxItems) {
$this->values[$key] = null;
}
}
}
if (!$this->maxItems) {
$this->values[$key] = null;
}
}
}
return $value;
}
return $value;
}
private function validateKeys(array $keys): bool
{
foreach ($keys as $key) {
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
}
}
private function validateKeys(array $keys): bool
{
foreach ($keys as $key) {
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
}
}
return true;
}
return true;
}
}

View File

@@ -17291,7 +17291,7 @@ class TCPDF {
}
/**
* Serialize data to be used with TCPDF tag in HTML code.
* SerializeToDOMNode data to be used with TCPDF tag in HTML code.
* @param string $method TCPDF method name
* @param array $params Method parameters
* @return string Serialized data

View File

@@ -49,9 +49,9 @@ class DashboardController extends Controller
// TODO 3.3 This is not the place to register this service, do better please
ServiceLocator::GetInstance()->RegisterService('ModelReflection', new ModelReflectionRuntime());
if (!empty($aValues)) {
$oDashlet->FromDenormalizedParams($aValues);
$oDashlet->FromModelData($aValues);
} else {
$aValues = $oDashlet->GetDenormalizedProperties();
$aValues = $oDashlet->GetModelData();
}
// TODO 3.3 Removing bEditMode for dashlet rendering fixes id having an "_edit" issues, but is it the right solution ?
@@ -89,28 +89,27 @@ class DashboardController extends Controller
public function OperationSave()
{
$sValues = utils::ReadParam('values', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$aValues = !empty($sValues) ? json_decode($sValues, true, 20) : [];
$sStatus = 'error';
$sMessage = 'Unknown error';
$sViewData = utils::ReadParam('values', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$aViewData = !empty($sViewData) ? json_decode($sViewData, true, 20) : [];
try {
// Get the form block from the service (and the compiler)
$oRequest = $this->getRequest();
$oFormBlock = FormBlockService::GetInstance()->GetFormBlockById('DashboardGrid', 'Dashboard');
$oBuilder = $this->GetFormBuilder($oFormBlock, $aValues);
$oBuilder = $this->GetFormBuilder($oFormBlock, $aViewData);
$oForm = $oBuilder->getForm();
$oForm->handleRequest($oRequest);
// We are in the submit action, so we submit the form with the provided values
$oForm->submit($aValues);
$oForm->submit($aViewData);
// TODO 3.3 Validate the form, it requires CSRF + stripping extra fields
// See $oForm->getErrors(true) to get all errors
if ($oForm->isSubmitted() && (true || $oForm->isValid())) {
// Save XML
$oDashboard = new RuntimeDashboard($aValues['id']);
$aModelData = $oForm->getData();
$oDashboard = new RuntimeDashboard($aModelData['id']);
$oDomNode = $oDashboard->CreateEmptyDashboard();
XMLSerializer::GetInstance()->Serialize($aValues, $oDomNode, 'DashboardGrid', 'Dashboard');
XMLSerializer::GetInstance()->Serialize($aModelData, $oDomNode, 'DashboardGrid', 'Dashboard');
$sXml = $oDomNode->ownerDocument->saveXML();
$oDashboard->PersistDashboard($sXml);
$sStatus = 'ok';

View File

@@ -28,7 +28,7 @@ class DashboardLayoutGrid extends \DashboardLayout
if ($oDashlet) {
$sDashletId = $oDashlet->GetID();
$sDashletClass = $oDashlet->GetDashletType();
$aDashletDenormalizedProperties = $oDashlet->GetDenormalizedProperties();
$aDashletDenormalizedProperties = $oDashlet->GetModelData();
$aDashletsInfo = DashletService::GetInstance()->GetDashletDefinition($sDashletClass);
// Also set minimal height/width

View File

@@ -944,7 +944,7 @@ JS
*/
private function GetDashletObjectListAppUserPreferencesPrefix(DashletObjectList $oDashlet, $aExtraParams, $sDashletId)
{
$sDataTableId = Dashlet::APPUSERPREFERENCES_PREFIX.$sDashletId;
$sDataTableId = Dashlet::APP_USER_PREFERENCES_PREFIX.$sDashletId;
$aClassAliases = [];
try {
$oFilter = $oDashlet->GetDBSearch($aExtraParams);

View File

@@ -64,7 +64,7 @@ class DashletObjectList extends Dashlet
$oBlock = new DisplayBlock($oFilter, 'list');
$aParams = [
'menu' => $sShowMenu,
'table_id' => self::APPUSERPREFERENCES_PREFIX.$this->sId,
'table_id' => self::APP_USER_PREFERENCES_PREFIX.$this->sId,
'surround_with_panel' => true,
'max_height' => '500px',
"panel_title" => Dict::S($sTitle),

View File

@@ -7,11 +7,7 @@
namespace Combodo\iTop\Application\Dashlet;
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletContainer;
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\UIBlock;
use Combodo\iTop\Application\WebPage\WebPage;
@@ -38,7 +34,7 @@ use utils;
abstract class Dashlet
{
/** @var string */
public const APPUSERPREFERENCES_PREFIX = 'Dashlet';
public const APP_USER_PREFERENCES_PREFIX = 'Dashlet';
protected $oModelReflection;
protected $sId;
@@ -203,9 +199,10 @@ abstract class Dashlet
$this->OnUpdate();
}
public function FromDenormalizedParams(array $aDenormalizedParams)
public function FromModelData(array $aModelData)
{
$this->aProperties = XMLNormalizer::GetInstance()->Normalize($aDenormalizedParams, $this->sDashletType, 'Dashlet');
$this->aProperties = $aModelData;
// $this->aProperties = XMLNormalizer::GetInstance()->Normalize($aModelData, $this->sDashletType, 'Dashlet');
$this->OnUpdate();
}
@@ -331,7 +328,10 @@ EOF
*
* @return mixed
*/
abstract public function GetPropertiesFields(\DesignerForm $oForm);
public function GetPropertiesFields(\DesignerForm $oForm)
{
return null;
}
/**
* @param \DOMNode $oContainerNode
@@ -514,8 +514,9 @@ EOF
$this->sDashletType = $sDashletType;
}
public function GetDenormalizedProperties(): ?array
public function GetModelData(): ?array
{
return XMLNormalizer::GetInstance()->Denormalize($this->aProperties, $this->sDashletType, 'Dashlet');
return $this->aProperties;
//return XMLNormalizer::GetInstance()->Denormalize($this->aProperties, $this->sDashletType, 'Dashlet');
}
}

View File

@@ -72,32 +72,11 @@ PHP;
public function SerializeToDOMNode(mixed $value, DesignElement$oDOMNode): void
{
$this->oValueType->SerializeToDOMNode($value, $oDOMNode);
$this->oValueType->SerializeToDOMNode(null, $value, $oDOMNode);
}
public function UnserializeFromDOMNode(DesignElement $oDOMNode): mixed
public function DeserializeFromDOMNode(DesignElement $oDOMNode): mixed
{
return $this->oValueType->UnserializeFromDOMNode($oDOMNode);
return $this->oValueType->DeserializeFromDOMNode($oDOMNode);
}
public function Normalize(mixed $value): mixed
{
return $this->oValueType->Normalize($value);
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode): void
{
$this->oValueType->EncodeToDOMNode($normalizedValue, $oDOMNode);
}
public function DecodeFromDomNode(DesignElement $oDOMNode): mixed
{
return $this->oValueType->DecodeFromDomNode($oDOMNode);
}
public function Denormalize(mixed $normalizedValue): mixed
{
return $this->oValueType->Denormalize($normalizedValue);
}
}

View File

@@ -1,58 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\Compiler\PropertyTypeCompiler;
class XMLEncoder
{
private static XMLEncoder $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): XMLEncoder
{
if (!isset(static::$oInstance)) {
static::$oInstance = new XMLEncoder();
}
return static::$oInstance;
}
public function Encode(mixed $normalizedValue, DesignElement $oParentNode, string $sId, string $sType): void
{
$sPropertyTypeXML = PropertyTypeCompiler::GetInstance()->GetXMLContent($sId, $sType);
$this->EncodeForPropertyType($normalizedValue, $oParentNode, $sPropertyTypeXML);
}
public function Decode(DesignElement $oDOMNode, string $sId, string $sType): mixed
{
$sPropertyTypeXML = PropertyTypeCompiler::GetInstance()->GetXMLContent($sId, $sType);
return $this->DecodeForPropertyType($oDOMNode, $sPropertyTypeXML);
}
public function EncodeForPropertyType(mixed $normalizedValue, DesignElement $oParentNode, string $sPropertyTypeXML): void
{
$oPropertyType = PropertyTypeCompiler::GetInstance()->CompilePropertyTypeFromXML($sPropertyTypeXML);
$oPropertyType->EncodeToDOMNode($normalizedValue, $oParentNode);
}
public function DecodeForPropertyType(DesignElement $oParentNode, string $sPropertyTypeXML): mixed
{
$oPropertyType = PropertyTypeCompiler::GetInstance()->CompilePropertyTypeFromXML($sPropertyTypeXML);
return $oPropertyType->DecodeFromDomNode($oParentNode);
}
}

View File

@@ -16,11 +16,8 @@ abstract class AbstractXMLFormat
{
}
abstract public function Normalize($value, AbstractValueType $oValueType): mixed;
abstract public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement $oDOMNode, AbstractValueType $oValueType): void;
abstract public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode, AbstractValueType $oValueType): void;
abstract public function DeserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed;
abstract public function DecodeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed;
abstract public function Denormalize($normalizedValue, AbstractValueType $oValueType): mixed;
}

View File

@@ -12,32 +12,20 @@ use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
class XMLFormatCSV extends AbstractXMLFormat
{
public function Normalize($value, AbstractValueType $oValueType): mixed
public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement $oDOMNode, AbstractValueType $oValueType): void
{
return $value;
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode, AbstractValueType $oValueType): void
{
if (is_array($normalizedValue)) {
$normalizedValue = implode(',', $normalizedValue);
if (is_null($sPropertyName)) {
$oTextNode = $oDOMNode->ownerDocument->createTextNode(implode(',', $value));
$oDOMNode->appendChild($oTextNode);
} else {
$oPropertyNode = $oDOMNode->ownerDocument->createElement($sPropertyName, implode(',', $value));
$oDOMNode->appendChild($oPropertyNode);
}
$oTextNode = $oDOMNode->ownerDocument->createTextNode($normalizedValue);
$oDOMNode->appendChild($oTextNode);
}
public function DecodeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
public function DeserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
{
$value = $oDOMNode->GetText('');
return explode(',', $value);
}
public function Denormalize($normalizedValue, AbstractValueType $oValueType): mixed
{
if (is_string($normalizedValue)) {
return explode(',', $normalizedValue);
}
return $normalizedValue;
}
}

View File

@@ -22,35 +22,34 @@ class XMLFormatCollectionWithId extends AbstractXMLFormat
$this->sTagName = $oDomNode->GetChildText('tag-name');
}
public function Normalize($value, AbstractValueType $oValueType): mixed
{
return $value;
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode, AbstractValueType $oValueType): void
public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement $oDOMNode, AbstractValueType $oValueType): void
{
if (!$oValueType instanceof ValueTypeCollection) {
throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes');
}
foreach ($normalizedValue as $sItemId => $aValues) {
/** @var DesignElement $oNode */
$oNode = $oDOMNode->ownerDocument->createElement($this->sTagName);
$oNode->setAttribute('id', $sItemId);
$oDOMNode->appendChild($oNode);
if (!is_null($sPropertyName)) {
$oPropertyNode = $oDOMNode->ownerDocument->createElement($sPropertyName);
$oDOMNode->appendChild($oPropertyNode);
} else {
$oPropertyNode = $oDOMNode;
}
foreach ($value as $sItemId => $aValues) {
/** @var DesignElement $oItemNode */
$oItemNode = $oPropertyNode->ownerDocument->createElement($this->sTagName);
$oItemNode->setAttribute('id', $sItemId);
$oPropertyNode->appendChild($oItemNode);
foreach ($oValueType->GetChildren() as $oChild) {
$sPropertyId = $oChild->GetId();
if (isset($aValues[$sPropertyId])) {
/** @var DesignElement $oSubNode */
$oSubNode = $oDOMNode->ownerDocument->createElement($sPropertyId);
$oNode->appendChild($oSubNode);
$oChild->EncodeToDOMNode($aValues[$sPropertyId], $oSubNode);
$oChild->SerializeToDOMNode($sPropertyId, $aValues[$sPropertyId], $oItemNode);
}
}
}
}
public function DecodeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
public function DeserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
{
if (!$oValueType instanceof ValueTypeCollection) {
throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes');
@@ -63,16 +62,11 @@ class XMLFormatCollectionWithId extends AbstractXMLFormat
$sItemId = $oNode->getAttribute('id');
$aSubArray = [];
foreach ($oValueType->GetChildren() as $oChild) {
$aSubArray[$oChild->GetId()] = $oChild->DecodeFromDomNode($oNode->GetUniqueElement($oChild->GetId()));
$aSubArray[$oChild->GetId()] = $oChild->DeserializeFromDOMNode($oNode->GetUniqueElement($oChild->GetId()));
}
$aNormalizedValues[$sItemId] = $aSubArray;
}
return $aNormalizedValues;
}
public function Denormalize($normalizedValue, AbstractValueType $oValueType): mixed
{
return $normalizedValue;
}
}

View File

@@ -33,14 +33,36 @@ class XMLFormatFlatArray extends AbstractXMLFormat
$this->sCountTag = $sCountTag;
}
public function UnserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement $oDOMNode, AbstractValueType $oValueType): void
{
$aResults = [];
if (!$oValueType instanceof ValueTypeCollection) {
throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes');
}
$oNode = $oDOMNode->ownerDocument->createElement($this->sCountTag, count($value));
$oDOMNode->appendChild($oNode);
foreach ($value as $iRank => $aValues) {
foreach ($oValueType->GetChildren() as $oChild) {
$sId = $oChild->GetId();
if (isset($aValues[$sId])) {
$sTagName = \MetaModel::ApplyParams($this->sTagFormat, ['rank' => $iRank, 'id' => $sId]);
$oNode = $oDOMNode->ownerDocument->createElement($sTagName, $aValues[$sId]);
$oDOMNode->appendChild($oNode);
}
}
}
}
public function DeserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
{
if (!$oValueType instanceof ValueTypeCollection) {
throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes');
}
$aResults = [];
$iCount = $oDOMNode->GetUniqueElement($this->sCountTag)->GetText(0);
for ($iRank = 0; $iRank < $iCount; $iRank++) {
foreach ($oValueType->GetChildren() as $oChild) {
@@ -55,85 +77,4 @@ class XMLFormatFlatArray extends AbstractXMLFormat
return $aResults;
}
public function Normalize($value, AbstractValueType $oValueType): mixed
{
if (!$oValueType instanceof ValueTypeCollection) {
throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes');
}
$aNormalizedValues = [];
$aNormalizedValues[$this->sCountTag] = count($value);
foreach ($value as $iRank => $aValues) {
foreach ($oValueType->GetChildren() as $oChild) {
$sId = $oChild->GetId();
if (isset($aValues[$sId])) {
$sTagName = \MetaModel::ApplyParams($this->sTagFormat, ['rank' => $iRank, 'id' => $sId]);
$aNormalizedValues[$sTagName] = $oChild->Normalize($aValues[$sId]);
}
}
}
return $aNormalizedValues;
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode, AbstractValueType $oValueType): void
{
if (!$oValueType instanceof ValueTypeCollection) {
throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes');
}
foreach ($normalizedValue as $sTag => $value) {
$oNode = $oDOMNode->ownerDocument->createElement($sTag, $value);
$oDOMNode->appendChild($oNode);
}
}
public function DecodeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
{
if (!$oValueType instanceof ValueTypeCollection) {
throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes');
}
$aNormalizedValues = [];
$iCount = $oDOMNode->GetUniqueElement($this->sCountTag)->GetText(0);
$aNormalizedValues[$this->sCountTag] = $iCount;
for ($iRank = 0; $iRank < $iCount; $iRank++) {
foreach ($oValueType->GetChildren() as $oChild) {
$sId = $oChild->GetId();
$sTagName = \MetaModel::ApplyParams($this->sTagFormat, ['rank' => $iRank, 'id' => $sId]);
$oChildNode = $oDOMNode->GetOptionalElement($sTagName);
if ($oChildNode) {
$aNormalizedValues[$sTagName] = $oChildNode->GetText('');
}
}
}
return $aNormalizedValues;
}
public function Denormalize($normalizedValue, AbstractValueType $oValueType): mixed
{
if (!$oValueType instanceof ValueTypeCollection) {
throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes');
}
$aValues = [];
$iCount = $normalizedValue[$this->sCountTag];
for ($iRank = 0; $iRank < $iCount; $iRank++) {
foreach ($oValueType->GetChildren() as $oChild) {
$sId = $oChild->GetId();
$sTagName = \MetaModel::ApplyParams($this->sTagFormat, ['rank' => $iRank, 'id' => $sId]);
if (isset($normalizedValue[$sTagName])) {
$aValues[$iRank][$sId] = $normalizedValue[$sTagName];
}
}
}
return $aValues;
}
}

View File

@@ -25,55 +25,36 @@ class XMLFormatValueAsId extends AbstractXMLFormat
$this->sTagName = $sTagName;
}
public function SerializeToDOMNode($value, $oDOMNode, AbstractValueType $oValueType): void
public function SerializeToDOMNode(?string $sPropertyName, $value, $oDOMNode, AbstractValueType $oValueType): void
{
if (!is_null($sPropertyName)) {
$oPropertyNode = $oDOMNode->ownerDocument->createElement($sPropertyName);
$oDOMNode->appendChild($oPropertyNode);
} else {
$oPropertyNode = $oDOMNode;
}
foreach ($value as $item) {
$oChildNode = $oDOMNode->ownerDocument->createElement($this->sTagName);
$oChildNode->setAttribute('id', "$item");
$oDOMNode->appendChild($oChildNode);
$oPropertyNode->appendChild($oChildNode);
}
}
public function UnserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
public function DeserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
{
$aResult = [];
foreach ($oDOMNode->getElementsByTagName($this->sTagName) as $oNode) {
foreach ($oDOMNode->childNodes as $oNode) {
if (!$oNode instanceof DesignElement) {
continue;
}
if ($oNode->tagName !== $this->sTagName) {
continue;
}
$sValue = $oNode->getAttribute('id');
$aResult[] = $sValue;
}
return $aResult;
}
public function Normalize($value, AbstractValueType $oValueType): mixed
{
return $value;
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode, AbstractValueType $oValueType): void
{
foreach ($normalizedValue as $item) {
$oChildNode = $oDOMNode->ownerDocument->createElement($this->sTagName);
$oChildNode->setAttribute('id', "$item");
$oDOMNode->appendChild($oChildNode);
}
}
public function DecodeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
{
$aResult = [];
foreach ($oDOMNode->getElementsByTagName($this->sTagName) as $oNode) {
$sValue = $oNode->getAttribute('id');
$aResult[] = $sValue;
}
return $aResult;
}
public function Denormalize($normalizedValue, AbstractValueType $oValueType): mixed
{
return $normalizedValue;
}
}

View File

@@ -1,58 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\Compiler\PropertyTypeCompiler;
class XMLNormalizer
{
private static XMLNormalizer $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): XMLNormalizer
{
if (!isset(static::$oInstance)) {
static::$oInstance = new XMLNormalizer();
}
return static::$oInstance;
}
public function Normalize(mixed $value, string $sId, string $sType): mixed
{
$sPropertyTypeXML = PropertyTypeCompiler::GetInstance()->GetXMLContent($sId, $sType);
return $this->NormalizeForPropertyType($value, $sPropertyTypeXML);
}
public function Denormalize(mixed $normalizedValue, string $sId, string $sType): mixed
{
$sPropertyTypeXML = PropertyTypeCompiler::GetInstance()->GetXMLContent($sId, $sType);
return $this->DenormalizeForPropertyType($normalizedValue, $sPropertyTypeXML);
}
public function NormalizeForPropertyType(mixed $normalizedValue, string $sPropertyTypeXML): mixed
{
$oPropertyType = PropertyTypeCompiler::GetInstance()->CompilePropertyTypeFromXML($sPropertyTypeXML);
return $oPropertyType->Normalize($normalizedValue);
}
public function DenormalizeForPropertyType(mixed $normalizedValue, string $sPropertyTypeXML): mixed
{
$oPropertyType = PropertyTypeCompiler::GetInstance()->CompilePropertyTypeFromXML($sPropertyTypeXML);
return $oPropertyType->Denormalize($normalizedValue);
}
}

View File

@@ -45,15 +45,15 @@ class XMLSerializer
public function SerializeForPropertyType(mixed $value, DesignElement $oParentNode, string $sPropertyTypeXML): void
{
$normalizedValue = XMLNormalizer::GetInstance()->NormalizeForPropertyType($value, $sPropertyTypeXML);
$oPropertyType = PropertyTypeCompiler::GetInstance()->CompilePropertyTypeFromXML($sPropertyTypeXML);
XMLEncoder::GetInstance()->EncodeForPropertyType($normalizedValue, $oParentNode, $sPropertyTypeXML);
$oPropertyType->SerializeToDOMNode($value, $oParentNode);
}
public function DeserializeForPropertyType(DesignElement $oParentNode, string $sPropertyTypeXML): mixed
{
$normalizedValue = XMLEncoder::GetInstance()->DecodeForPropertyType($oParentNode, $sPropertyTypeXML);
$oPropertyType = PropertyTypeCompiler::GetInstance()->CompilePropertyTypeFromXML($sPropertyTypeXML);
return XMLNormalizer::GetInstance()->DenormalizeForPropertyType($normalizedValue, $sPropertyTypeXML);
return $oPropertyType->DeserializeFromDOMNode($oParentNode);
}
}

View File

@@ -233,24 +233,19 @@ PHP;
return $this->oParent->GetChild($sId);
}
public function Normalize(mixed $value): mixed
public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement$oDOMNode): void
{
return $value;
if (is_null($sPropertyName)) {
$oTextNode = $oDOMNode->ownerDocument->createTextNode($value);
$oDOMNode->appendChild($oTextNode);
} else {
$oPropertyNode = $oDOMNode->ownerDocument->createElement($sPropertyName, $value);
$oDOMNode->appendChild($oPropertyNode);
}
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode): void
{
$oTextNode = $oDOMNode->ownerDocument->createTextNode($normalizedValue);
$oDOMNode->appendChild($oTextNode);
}
public function DecodeFromDomNode(DesignElement $oDOMNode): mixed
public function DeserializeFromDOMNode(DesignElement $oDOMNode): mixed
{
return $oDOMNode->GetText();
}
public function Denormalize(mixed $normalizedValue): mixed
{
return $normalizedValue;
}
}

View File

@@ -82,23 +82,13 @@ class ValueTypeCollection extends ValueTypePropertyTree
return $this->GetLocalPHPForValueType();
}
public function Normalize(mixed $value): mixed
public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement$oDOMNode): void
{
return $this->oXMLFormat->Normalize($value, $this);
$this->oXMLFormat->SerializeToDOMNode($sPropertyName, $value, $oDOMNode, $this);
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode): void
public function DeserializeFromDOMNode(DesignElement $oDOMNode): mixed
{
$this->oXMLFormat->EncodeToDOMNode($normalizedValue, $oDOMNode, $this);
}
public function DecodeFromDomNode(DesignElement $oDOMNode): mixed
{
return $this->oXMLFormat->DecodeFromDOMNode($oDOMNode, $this);
}
public function Denormalize(mixed $normalizedValue): mixed
{
return $this->oXMLFormat->Denormalize($normalizedValue, $this);
return $this->oXMLFormat->DeserializeFromDOMNode($oDOMNode, $this);
}
}

View File

@@ -29,25 +29,32 @@ class ValueTypePolymorphic extends AbstractBranchValueType
return "// ValueTypePolymorphic Block\n";
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode): void
public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement $oDOMNode): void
{
$sType = $normalizedValue['type'];
$oDOMNode->setAttribute('xsi:type', $sType);
if (!is_null($sPropertyName)) {
$oPropertyNode = $oDOMNode->ownerDocument->createElement($sPropertyName);
$oDOMNode->appendChild($oPropertyNode);
} else {
$oPropertyNode = $oDOMNode;
}
$sType = $value['type'];
$oPropertyNode->setAttribute('xsi:type', $sType);
$oPropertyType = PropertyTypeService::GetInstance()->GetPropertyType($sType);
$aProperties = $normalizedValue['properties'];
$oPropertyType->EncodeToDOMNode($aProperties, $oDOMNode);
$aProperties = $value['properties'];
$oPropertyType->SerializeToDOMNode($aProperties, $oPropertyNode);
}
public function DecodeFromDomNode(DesignElement $oDOMNode): mixed
public function DeserializeFromDOMNode(DesignElement $oDOMNode): mixed
{
$sType = $oDOMNode->getAttribute('xsi:type');
$oPropertyType = PropertyTypeService::GetInstance()->GetPropertyType($sType);
return [
'type' => $sType,
'properties' => $oPropertyType->DecodeFromDOMNode($oDOMNode),
'properties' => $oPropertyType->DeserializeFromDOMNode($oDOMNode),
];
}
}

View File

@@ -75,20 +75,23 @@ PHP;
return $this->GetLocalPHPForValueType($this->sSubTreeClass);
}
public function SerializeToDOMNode(mixed $value, DesignElement $oDOMNode): void
public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement $oDOMNode): void
{
if (!is_null($sPropertyName)) {
$oPropertyNode = $oDOMNode->ownerDocument->createElement($sPropertyName);
$oDOMNode->appendChild($oPropertyNode);
} else {
$oPropertyNode = $oDOMNode;
}
foreach ($this->aChildren as $oChild) {
$sId = $oChild->sId;
if (isset($value[$sId])) {
/** @var DesignElement $oChildNode */
$oChildNode = $oDOMNode->ownerDocument->createElement($sId);
$oDOMNode->appendChild($oChildNode);
$oChild->SerializeToDOMNode($value[$sId], $oChildNode);
$oChild->SerializeToDOMNode($sId, $value[$sId], $oPropertyNode);
}
}
}
public function UnserializeFromDOMNode(DesignElement $oDOMNode): mixed
public function DeserializeFromDOMNode(DesignElement $oDOMNode): mixed
{
$aResults = [];
@@ -96,66 +99,13 @@ PHP;
$sId = $oChild->sId;
$oChildNode = $oDOMNode->GetOptionalElement($sId);
if ($oChildNode) {
$aResults[$sId] = $oChild->UnserializeFromDOMNode($oChildNode);
$aResults[$sId] = $oChild->DeserializeFromDOMNode($oChildNode);
} else {
// For flat arrays, no node with $sId is present
$aResults[$sId] = $oChild->DeserializeFromDOMNode($oDOMNode);
}
}
return $aResults;
}
public function Normalize(mixed $value): mixed
{
$aNormalizedValues = [];
foreach ($this->aChildren as $oChild) {
$sId = $oChild->sId;
if (isset($value[$sId])) {
$aNormalizedValues[$sId] = $oChild->Normalize($value[$sId]);
}
}
return $aNormalizedValues;
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode): void
{
foreach ($this->aChildren as $oChild) {
$sId = $oChild->sId;
if (isset($normalizedValue[$sId])) {
/** @var DesignElement $oChildNode */
$oChildNode = $oDOMNode->ownerDocument->createElement($sId);
$oDOMNode->appendChild($oChildNode);
$oChild->EncodeToDOMNode($normalizedValue[$sId], $oChildNode);
}
}
}
public function DecodeFromDomNode(DesignElement $oDOMNode): mixed
{
$aNormalizedValue = [];
foreach ($this->aChildren as $oChild) {
$sId = $oChild->sId;
$oChildNode = $oDOMNode->GetOptionalElement($sId);
if ($oChildNode) {
$aNormalizedValue[$sId] = $oChild->DecodeFromDomNode($oChildNode);
}
}
return $aNormalizedValue;
}
public function Denormalize(mixed $normalizedValue): mixed
{
$aValues = [];
foreach ($this->aChildren as $oChild) {
$sId = $oChild->sId;
if (isset($normalizedValue[$sId])) {
$aValues[$sId] = $oChild->Denormalize($normalizedValue[$sId]);
}
}
return $aValues;
}
}

View File

@@ -54,23 +54,13 @@ class ValueTypeCollectionOfValues extends AbstractLeafValueType
return $this->oRealValueType->ToPHPFormBlock($aPHPFragments);
}
public function Normalize(mixed $value): mixed
public function SerializeToDOMNode(?string $sPropertyName, mixed $value, DesignElement $oDOMNode): void
{
return $this->oXMLFormat->Normalize($value, $this);
$this->oXMLFormat->SerializeToDOMNode($sPropertyName, $value, $oDOMNode, $this);
}
public function EncodeToDOMNode(mixed $normalizedValue, DesignElement $oDOMNode): void
public function DeserializeFromDOMNode(DesignElement $oDOMNode): mixed
{
$this->oXMLFormat->EncodeToDOMNode($normalizedValue, $oDOMNode, $this);
}
public function DecodeFromDomNode(DesignElement $oDOMNode): mixed
{
return $this->oXMLFormat->DecodeFromDOMNode($oDOMNode, $this);
}
public function Denormalize(mixed $normalizedValue): mixed
{
return $this->oXMLFormat->Denormalize($normalizedValue, $this);
return $this->oXMLFormat->DeserializeFromDOMNode($oDOMNode, $this);
}
}

View File

@@ -1,170 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\PropertyType\Compiler\PropertyTypeCompiler;
use Combodo\iTop\PropertyType\PropertyTypeDesign;
use Combodo\iTop\Service\DependencyInjection\ServiceLocator;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
class XMLNormalizerTest extends ItopDataTestCase
{
/**
* @dataProvider XMLNormalizerProvider
*
* @param $denormalizedValue
* @param string $sPropertyTypeXML
* @param string $normalizedValue
*
* @return void
* @throws \DOMException
*/
public function testNormalizeXML($denormalizedValue, string $sPropertyTypeXML, $normalizedValue)
{
ServiceLocator::GetInstance()->RegisterService('ModelReflection', new ModelReflectionRuntime());
$oDOMDocument = new PropertyTypeDesign();
$oDOMDocument->preserveWhiteSpace = false;
$oDOMDocument->formatOutput = true;
/** @var \Combodo\iTop\DesignElement $oRootNode */
$oRootNode = $oDOMDocument->createElement('root');
$oDOMDocument->appendChild($oRootNode);
$actualValue = Combodo\iTop\PropertyType\Serializer\XMLNormalizer::GetInstance()->NormalizeForPropertyType($denormalizedValue, $sPropertyTypeXML);
$this->assertEquals($normalizedValue, $actualValue);
}
public function XMLNormalizerProvider()
{
return [
'Basic test should serialize to XML' => [
'denormalizedValue' => 'text',
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="basic_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-Label">
</definition>
</property_type>
XML,
'normalizedValue' => 'text',
],
'Collection of values as CSV' => [
'denormalizedValue' => ['Contact', 'Organization'],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="basic_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-CollectionOfValues">
<xml-format xsi:type="Combodo-XMLFormat-CSV"/>
<value-type xsi:type="Combodo-ValueType-Class">
</value-type>
</definition>
</property_type>
XML,
'normalizedValue' => ['Contact', 'Organization'],
],
'Collection of values as id attribute' => [
'denormalizedValue' => ['Contact', 'Organization'],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="class_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-CollectionOfValues">
<xml-format xsi:type="Combodo-XMLFormat-ValueAsId">
<tag-name>item</tag-name>
</xml-format>
<value-type xsi:type="Combodo-ValueType-Class">
</value-type>
</definition>
</property_type>
XML,
'normalizedValue' => ['Contact', 'Organization'],
],
'Collection of tree as flat array' => [
'denormalizedValue' => [
[
'title_property' => 'title_a',
'class_property' => 'class_a',
],
[
'title_property' => 'title_b',
'class_property' => 'class_b',
],
],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="collection_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-Collection">
<xml-format xsi:type="Combodo-XMLFormat-FlatArray">
<count-tag>item_count</count-tag>
<tag-format>item_\$rank\$_\$id\$</tag-format>
</xml-format>
<prototype>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</prototype>
</definition>
</property_type>
XML,
'normalizedValue' => [
'item_count' => 2,
'item_0_title_property' => 'title_a',
'item_0_class_property' => 'class_a',
'item_1_title_property' => 'title_b',
'item_1_class_property' => 'class_b',
],
],
'Property tree' => [
'denormalizedValue' => ['title_property' => 'title', 'class_property' => 'class'],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="property_tree_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</nodes>
</definition>
</property_type>
XML,
'normalizedValue' => ['title_property' => 'title', 'class_property' => 'class'],
],
];
}
/**
* @dataProvider XMLNormalizerProvider
*
* @param $sInputXMLContent
* @param string $sPropertyTypeXML
* @param $expectedValue
*
* @return void
*/
public function testDenormalizeXML($denormalizedValue, string $sPropertyTypeXML, $normalizedValue)
{
ServiceLocator::GetInstance()->RegisterService('ModelReflection', new ModelReflectionRuntime());
$aActualValue = Combodo\iTop\PropertyType\Serializer\XMLNormalizer::GetInstance()->DenormalizeForPropertyType($normalizedValue, $sPropertyTypeXML);
$this->assertEquals($denormalizedValue, $aActualValue);
}
}

View File

@@ -145,21 +145,40 @@ XML,
</root>
XML,
],
'Property tree' => [
'normalizedValue' => ['title_property' => 'title', 'class_property' => 'class'],
'Collection of tree as flat array in a property tree' => [
'normalizedValue' => [
'collection' => [
[
'title_property' => 'title_a',
'class_property' => 'class_a',
],
[
'title_property' => 'title_b',
'class_property' => 'class_b',
],
],
],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="property_tree_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<property_type id="collection_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="collection" xsi:type="Combodo-ValueType-Collection">
<xml-format xsi:type="Combodo-XMLFormat-FlatArray">
<count-tag>item_count</count-tag>
<tag-format>item_\$rank\$_\$id\$</tag-format>
</xml-format>
<prototype>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</prototype>
</node>
</nodes>
</definition>
</property_type>
@@ -167,8 +186,11 @@ XML,
'sXMLContent' => <<<XML
<?xml version="1.0"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<title_property>title</title_property>
<class_property>class</class_property>
<item_count>2</item_count>
<item_0_title_property>title_a</item_0_title_property>
<item_0_class_property>class_a</item_0_class_property>
<item_1_title_property>title_b</item_1_title_property>
<item_1_class_property>class_b</item_1_class_property>
</root>
XML,
],
@@ -243,6 +265,79 @@ XML,
<class_property>class_b</class_property>
</item>
</root>
XML,
],
'Property tree' => [
'normalizedValue' => [
'title_property' => 'title',
'sub_tree' => [
'sub_tree_property' => 'my_class',
],
'items' => [
'a' => [
'title_property' => 'title_a',
'class_property' => 'class_a',
],
'b' => [
'title_property' => 'title_b',
'class_property' => 'class_b',
],
],
],
'sPropertyTypeXML' => <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<property_type id="property_tree_test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Combodo-PropertyType" xsi:noNamespaceSchemaLocation = "https://www.combodo.com/itop-schema/3.3">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="sub_tree" xsi:type="Combodo-ValueType-PropertyTree">
<label>My sub tree</label>
<nodes>
<node id="sub_tree_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</nodes>
</node>
<node id="items" xsi:type="Combodo-ValueType-Collection">
<xml-format xsi:type="Combodo-XMLFormat-CollectionWithId">
<tag-name>item</tag-name>
</xml-format>
<prototype>
<node id="title_property" xsi:type="Combodo-ValueType-Label">
<label>UI:BasicTest:Prop-Title</label>
</node>
<node id="class_property" xsi:type="Combodo-ValueType-Class">
<label>UI:BasicTest:Prop-Class</label>
<categories-csv>test</categories-csv>
</node>
</prototype>
</node>
</nodes>
</definition>
</property_type>
XML,
'sXMLContent' => <<<XML
<?xml version="1.0"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<title_property>title</title_property>
<sub_tree>
<sub_tree_property>my_class</sub_tree_property>
</sub_tree>
<items>
<item id="a">
<title_property>title_a</title_property>
<class_property>class_a</class_property>
</item>
<item id="b">
<title_property>title_b</title_property>
<class_property>class_b</class_property>
</item>
</items>
</root>
XML,
],
];