Backup
This commit is contained in:
parent
965f56e0d4
commit
6f65735675
BIN
src/new/assets/images/changelog/screen1.png
Normal file
BIN
src/new/assets/images/changelog/screen1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
src/new/assets/images/changelog/screen2.png
Normal file
BIN
src/new/assets/images/changelog/screen2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
@ -27,6 +27,7 @@ return [
|
|||||||
PSC\Backend\DashboardBundle\PSCBackendDashboardBundle::class => ['all' => true],
|
PSC\Backend\DashboardBundle\PSCBackendDashboardBundle::class => ['all' => true],
|
||||||
PSC\Shop\SettingsBundle\PSCShopSettingsBundle::class => ['all' => true],
|
PSC\Shop\SettingsBundle\PSCShopSettingsBundle::class => ['all' => true],
|
||||||
PSC\System\SettingsBundle\PSCSystemSettingsBundle::class => ['all' => true],
|
PSC\System\SettingsBundle\PSCSystemSettingsBundle::class => ['all' => true],
|
||||||
|
PSC\System\ContentEngineBundle\PSCSystemContentEngineBundle::class => ['all' => true],
|
||||||
PSC\Backend\ToolsBundle\PSCBackendToolsBundle::class => ['all' => true],
|
PSC\Backend\ToolsBundle\PSCBackendToolsBundle::class => ['all' => true],
|
||||||
PSC\Shop\OrderBundle\PSCShopOrderBundle::class => ['all' => true],
|
PSC\Shop\OrderBundle\PSCShopOrderBundle::class => ['all' => true],
|
||||||
PSC\Shop\ContactBundle\PSCShopContactBundle::class => ['all' => true],
|
PSC\Shop\ContactBundle\PSCShopContactBundle::class => ['all' => true],
|
||||||
|
|||||||
@ -49,6 +49,8 @@ return static function (RoutingConfigurator $routingConfigurator): void {
|
|||||||
|
|
||||||
$routingConfigurator->import('@PSCSystemSettingsBundle/Resources/config/routing.yml');
|
$routingConfigurator->import('@PSCSystemSettingsBundle/Resources/config/routing.yml');
|
||||||
|
|
||||||
|
$routingConfigurator->import('@PSCSystemContentEngineBundle/Resources/config/routing.yml');
|
||||||
|
|
||||||
$routingConfigurator->import('@PSCSystemUpdateBundle/Resources/config/routing.yml');
|
$routingConfigurator->import('@PSCSystemUpdateBundle/Resources/config/routing.yml');
|
||||||
|
|
||||||
$routingConfigurator->import('@PSCShopPaymentBundle/Resources/config/routing.yml');
|
$routingConfigurator->import('@PSCShopPaymentBundle/Resources/config/routing.yml');
|
||||||
|
|||||||
@ -2603,9 +2603,11 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<ul>
|
<ul>
|
||||||
{% for option in position.calc.options %}
|
{% for option in position.calc.options %}
|
||||||
<li><b>{{option.name }}</b>: {{ option.value }}</li>
|
{% if option is not instanceof('\\PSC\\Library\\Calc\\Option\\Type\\Hidden') and option.valid %}
|
||||||
|
<li><b>{{option.name }}</b>: {{ option.value }}</li>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td valign="top">{{ position.count }}</td>
|
<td valign="top">{{ position.count }}</td>
|
||||||
<td valign="top" style="text-align: right">{{ position.obj.priceAllNetto|number_format(2, ',', '.') }}€</td>
|
<td valign="top" style="text-align: right">{{ position.obj.priceAllNetto|number_format(2, ',', '.') }}€</td>
|
||||||
|
|||||||
49
src/new/src/PSC/System/ContentEngineBundle/Api/Config.php
Normal file
49
src/new/src/PSC/System/ContentEngineBundle/Api/Config.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||||
|
use OpenApi\Attributes\JsonContent;
|
||||||
|
use OpenApi\Attributes\RequestBody;
|
||||||
|
use OpenApi\Attributes\Response;
|
||||||
|
use OpenApi\Attributes\Tag;
|
||||||
|
use PSC\Library\Calc\Engine;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Input\ConfigInput;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Output\ContentOutput;
|
||||||
|
use PSC\System\ContentEngineBundle\Service\ContentResolver;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class Config extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ContentResolver $contentResolver,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Load content configuration',
|
||||||
|
content: new JsonContent(ref: new Model(type: ContentOutput::class)),
|
||||||
|
)]
|
||||||
|
#[RequestBody(content: new JsonContent(ref: new Model(type: ConfigInput::class)))]
|
||||||
|
#[Tag(name: 'ContentEngine')]
|
||||||
|
#[Route(path: '/config', methods: ['POST'])]
|
||||||
|
public function config(#[MapRequestPayload] ConfigInput $data)
|
||||||
|
{
|
||||||
|
$xml = $this->contentResolver->loadXml($data->module, $data->id);
|
||||||
|
|
||||||
|
if (empty(trim($xml))) {
|
||||||
|
$xml = '<?xml version="1.0" encoding="utf-8"?><kalkulation><artikel><name></name></artikel></kalkulation>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$engine = new Engine();
|
||||||
|
$engine->loadString($xml);
|
||||||
|
|
||||||
|
$output = new ContentOutput();
|
||||||
|
$output->json = $engine->generateJson();
|
||||||
|
$output->xml = $engine->generateXML();
|
||||||
|
|
||||||
|
return $this->json($output);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/new/src/PSC/System/ContentEngineBundle/Api/Design.php
Normal file
46
src/new/src/PSC/System/ContentEngineBundle/Api/Design.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||||
|
use OpenApi\Attributes\JsonContent;
|
||||||
|
use OpenApi\Attributes\RequestBody;
|
||||||
|
use OpenApi\Attributes\Response;
|
||||||
|
use OpenApi\Attributes\Tag;
|
||||||
|
use PSC\Library\Calc\Engine;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Input\DesignInput;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Output\ContentOutput;
|
||||||
|
use PSC\System\ContentEngineBundle\Service\ContentResolver;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class Design extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ContentResolver $contentResolver,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Process JSON design',
|
||||||
|
content: new JsonContent(ref: new Model(type: ContentOutput::class)),
|
||||||
|
)]
|
||||||
|
#[RequestBody(content: new JsonContent(ref: new Model(type: DesignInput::class)))]
|
||||||
|
#[Tag(name: 'ContentEngine')]
|
||||||
|
#[Route(path: '/design', methods: ['POST'])]
|
||||||
|
public function design(#[MapRequestPayload] DesignInput $data)
|
||||||
|
{
|
||||||
|
$engine = new Engine();
|
||||||
|
$engine->loadJson(json_encode($data->json));
|
||||||
|
$engine->setVariables($data->values);
|
||||||
|
$engine->calc();
|
||||||
|
|
||||||
|
$output = new ContentOutput();
|
||||||
|
$output->json = $engine->generateJson();
|
||||||
|
$output->xml = $engine->generateXML();
|
||||||
|
$output->jsonGraph = json_decode($engine->getCalcGraph()->generateJsonGraph(), true);
|
||||||
|
|
||||||
|
return $this->json($output);
|
||||||
|
}
|
||||||
|
}
|
||||||
149
src/new/src/PSC/System/ContentEngineBundle/Api/Preview.php
Normal file
149
src/new/src/PSC/System/ContentEngineBundle/Api/Preview.php
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||||
|
use OpenApi\Attributes\JsonContent;
|
||||||
|
use OpenApi\Attributes\RequestBody;
|
||||||
|
use OpenApi\Attributes\Response;
|
||||||
|
use OpenApi\Attributes\Tag;
|
||||||
|
use PSC\Library\Calc\Engine;
|
||||||
|
use PSC\Library\Calc\Error\Validation\Input\Max;
|
||||||
|
use PSC\Library\Calc\Error\Validation\Input\Min as PSCMin;
|
||||||
|
use PSC\Library\Calc\Option\Type\Base;
|
||||||
|
use PSC\Library\Calc\Option\Type\DeliverySelect;
|
||||||
|
use PSC\Library\Calc\Option\Type\Select\Opt;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Input\PreviewInput;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Output\Element\Element;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Output\Element\Option;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Output\PreviewOutput;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class Preview extends AbstractController
|
||||||
|
{
|
||||||
|
#[Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Preview content form elements',
|
||||||
|
content: new JsonContent(ref: new Model(type: PreviewOutput::class)),
|
||||||
|
)]
|
||||||
|
#[RequestBody(content: new JsonContent(ref: new Model(type: PreviewInput::class)))]
|
||||||
|
#[Tag(name: 'ContentEngine')]
|
||||||
|
#[Route(path: '/preview', methods: ['POST'])]
|
||||||
|
public function preview(#[MapRequestPayload] PreviewInput $data)
|
||||||
|
{
|
||||||
|
$engine = new Engine();
|
||||||
|
$engine->loadJson(json_encode($data->json));
|
||||||
|
$engine->setVariables($data->values);
|
||||||
|
|
||||||
|
$output = new PreviewOutput();
|
||||||
|
|
||||||
|
/** @var Base $option */
|
||||||
|
foreach ($engine->getArticle()->getOptions() as $option) {
|
||||||
|
$output->elements[] = $this->parseOption($option);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseOption(Base $option): Element
|
||||||
|
{
|
||||||
|
$element = new Element();
|
||||||
|
if ($option->getName()) {
|
||||||
|
$element->name = $option->getName();
|
||||||
|
}
|
||||||
|
$element->required = $option->isRequire();
|
||||||
|
if (is_array($option->getRawValue())) {
|
||||||
|
$element->rawValues = $option->getRawValue();
|
||||||
|
} else {
|
||||||
|
$element->rawValue = (string) $option->getRawValue();
|
||||||
|
}
|
||||||
|
if ($option->getDefault()) {
|
||||||
|
$element->defaultValue = $option->getDefault();
|
||||||
|
}
|
||||||
|
$element->value = $option->getValue();
|
||||||
|
$element->help = $option->getHelp();
|
||||||
|
$element->helpLink = $option->getHelpLink();
|
||||||
|
$element->id = $option->getId();
|
||||||
|
$element->valid = $option->isValid();
|
||||||
|
$element->htmlType = $option->type;
|
||||||
|
$element->displayGroup = $option->getDisplayGroup();
|
||||||
|
|
||||||
|
if ($option->type == 'select' || $option->type == 'checkbox' || $option->type == 'radio') {
|
||||||
|
foreach ($option->getOptions() as $opt) {
|
||||||
|
$elementSelected = array_find((array) $option->getSelectedOptions(), function (Opt $o1) use ($opt) {
|
||||||
|
return $o1->getId() === $opt->getId();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($option instanceof DeliverySelect) {
|
||||||
|
$tmpOpt = new Option();
|
||||||
|
$tmpOpt->id = $opt->getId();
|
||||||
|
$tmpOpt->name = $opt->getLabel();
|
||||||
|
$tmpOpt->valid = $opt->isValid();
|
||||||
|
$tmpOpt->selected = $elementSelected ? true : false;
|
||||||
|
$tmpOpt->info = $opt->getInfo();
|
||||||
|
$tmpOpt->deliveryDate = $opt->getDeliveryDateAsString();
|
||||||
|
} else {
|
||||||
|
$tmpOpt = new Option();
|
||||||
|
$tmpOpt->id = $opt->getId();
|
||||||
|
$tmpOpt->name = $opt->getLabel();
|
||||||
|
$tmpOpt->valid = $opt->isValid();
|
||||||
|
$tmpOpt->selected = $elementSelected ? true : false;
|
||||||
|
}
|
||||||
|
$element->options[] = $tmpOpt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($option->type == 'input') {
|
||||||
|
$element->minValue = $option->getMinValue();
|
||||||
|
$element->maxValue = $option->getMaxValue();
|
||||||
|
$element->placeHolder = $option->getPlaceHolder();
|
||||||
|
$element->pattern = $option->getPattern();
|
||||||
|
foreach ($option->getValidationErrors() as $error) {
|
||||||
|
if ($error instanceof PSCMin) {
|
||||||
|
$element->validationErrors[] = [
|
||||||
|
'type' => 'min',
|
||||||
|
'value' => $element->value,
|
||||||
|
'min' => $option->getMinValue(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($error instanceof Max) {
|
||||||
|
$element->validationErrors[] = [
|
||||||
|
'type' => 'max',
|
||||||
|
'value' => $element->value,
|
||||||
|
'max' => $option->getMaxValue(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($option->type == 'row') {
|
||||||
|
foreach ($option->getColumns() as $column) {
|
||||||
|
$element->elements[] = $this->parseOption($column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($option->type == 'column') {
|
||||||
|
foreach ($option->getOptions() as $opt) {
|
||||||
|
$element->elements[] = $this->parseOption($opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseOpt(Opt $opt, ?array $selectedOptions): Option
|
||||||
|
{
|
||||||
|
$selected = $selectedOptions ? array_find($selectedOptions, fn(Opt $o) => $o->getId() === $opt->getId()) : null;
|
||||||
|
|
||||||
|
$option = new Option();
|
||||||
|
$option->id = $opt->getId();
|
||||||
|
$option->name = $opt->getLabel();
|
||||||
|
$option->prefix = $opt->getPrefix() ?? '';
|
||||||
|
$option->suffix = $opt->getSuffix() ?? '';
|
||||||
|
$option->valid = $opt->isValid();
|
||||||
|
$option->selected = $selected !== null;
|
||||||
|
|
||||||
|
return $option;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/new/src/PSC/System/ContentEngineBundle/Api/Save.php
Normal file
34
src/new/src/PSC/System/ContentEngineBundle/Api/Save.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||||
|
use OpenApi\Attributes\JsonContent;
|
||||||
|
use OpenApi\Attributes\RequestBody;
|
||||||
|
use OpenApi\Attributes\Response;
|
||||||
|
use OpenApi\Attributes\Tag;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Input\XmlInput;
|
||||||
|
use PSC\System\ContentEngineBundle\Service\ContentResolver;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
class Save extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ContentResolver $contentResolver,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Response(response: 200, description: 'Save XML to content entity')]
|
||||||
|
#[RequestBody(content: new JsonContent(ref: new Model(type: XmlInput::class)))]
|
||||||
|
#[Tag(name: 'ContentEngine')]
|
||||||
|
#[Route(path: '/save', methods: ['PUT'])]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
|
public function save(#[MapRequestPayload] XmlInput $data)
|
||||||
|
{
|
||||||
|
$this->contentResolver->saveXml($data->module, $data->id, $data->xml);
|
||||||
|
|
||||||
|
return $this->json(['success' => true]);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/new/src/PSC/System/ContentEngineBundle/Api/Xml.php
Normal file
45
src/new/src/PSC/System/ContentEngineBundle/Api/Xml.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||||
|
use OpenApi\Attributes\JsonContent;
|
||||||
|
use OpenApi\Attributes\RequestBody;
|
||||||
|
use OpenApi\Attributes\Response;
|
||||||
|
use OpenApi\Attributes\Tag;
|
||||||
|
use PSC\Library\Calc\Engine;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Input\XmlInput;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Output\ContentOutput;
|
||||||
|
use PSC\System\ContentEngineBundle\Service\ContentResolver;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class Xml extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ContentResolver $contentResolver,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Process XML input',
|
||||||
|
content: new JsonContent(ref: new Model(type: ContentOutput::class)),
|
||||||
|
)]
|
||||||
|
#[RequestBody(content: new JsonContent(ref: new Model(type: XmlInput::class)))]
|
||||||
|
#[Tag(name: 'ContentEngine')]
|
||||||
|
#[Route(path: '/xml', methods: ['POST'])]
|
||||||
|
public function xml(#[MapRequestPayload] XmlInput $data)
|
||||||
|
{
|
||||||
|
$engine = new Engine();
|
||||||
|
$engine->loadString($data->xml);
|
||||||
|
$engine->calc();
|
||||||
|
|
||||||
|
$output = new ContentOutput();
|
||||||
|
$output->json = json_decode($engine->generateJson(), true);
|
||||||
|
$output->xml = $engine->generateXML(true);
|
||||||
|
$output->jsonGraph = json_decode($engine->getCalcGraph()->generateJsonGraph(), true);
|
||||||
|
|
||||||
|
return $this->json($output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||||
|
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||||
|
|
||||||
|
class Configuration implements ConfigurationInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getConfigTreeBuilder(): TreeBuilder
|
||||||
|
{
|
||||||
|
$treeBuilder = new TreeBuilder('psc_system_content_engine_bundle');
|
||||||
|
return $treeBuilder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Component\Config\FileLocator;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Loader;
|
||||||
|
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||||
|
|
||||||
|
class PSCSystemContentEngineExtension extends Extension
|
||||||
|
{
|
||||||
|
public function load(array $configs, ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
$configuration = new Configuration();
|
||||||
|
$this->processConfiguration($configuration, $configs);
|
||||||
|
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
|
||||||
|
$loader->load('services.yml');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Dto\Input;
|
||||||
|
|
||||||
|
use OpenApi\Attributes\Property;
|
||||||
|
|
||||||
|
final class ConfigInput
|
||||||
|
{
|
||||||
|
#[Property(type: 'integer', description: 'ID of the content entity (e.g. CMS page)')]
|
||||||
|
public int $id;
|
||||||
|
|
||||||
|
#[Property(type: 'string', description: 'Module type (e.g. cms, form)')]
|
||||||
|
public string $module = 'cms';
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Dto\Input;
|
||||||
|
|
||||||
|
use OpenApi\Attributes\Items;
|
||||||
|
use OpenApi\Attributes\Property;
|
||||||
|
|
||||||
|
final class DesignInput
|
||||||
|
{
|
||||||
|
#[Property(type: 'integer', description: 'ID of the content entity')]
|
||||||
|
public int $id;
|
||||||
|
|
||||||
|
#[Property(type: 'string', description: 'Module type (e.g. cms, form)')]
|
||||||
|
public string $module = 'cms';
|
||||||
|
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'array', items: new Items()))]
|
||||||
|
public array $json = [];
|
||||||
|
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'string'))]
|
||||||
|
public array $values = [];
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Dto\Input;
|
||||||
|
|
||||||
|
use OpenApi\Attributes\Items;
|
||||||
|
use OpenApi\Attributes\Property;
|
||||||
|
|
||||||
|
final class PreviewInput
|
||||||
|
{
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'array', items: new Items()))]
|
||||||
|
public array $json = [];
|
||||||
|
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'string'))]
|
||||||
|
public array $values = [];
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Dto\Input;
|
||||||
|
|
||||||
|
use OpenApi\Attributes\Property;
|
||||||
|
|
||||||
|
final class XmlInput
|
||||||
|
{
|
||||||
|
#[Property(type: 'integer', description: 'ID of the content entity')]
|
||||||
|
public int $id;
|
||||||
|
|
||||||
|
#[Property(type: 'string', description: 'Module type (e.g. cms, form)')]
|
||||||
|
public string $module = 'cms';
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $xml = '';
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Dto\Output;
|
||||||
|
|
||||||
|
use OpenApi\Attributes\Items;
|
||||||
|
use OpenApi\Attributes\Property;
|
||||||
|
|
||||||
|
final class ContentOutput
|
||||||
|
{
|
||||||
|
#[Property(type: 'boolean')]
|
||||||
|
public bool $success = true;
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $json = '';
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $xml = '';
|
||||||
|
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'string'))]
|
||||||
|
public array $jsonGraph = [];
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Dto\Output\Element;
|
||||||
|
|
||||||
|
use OpenApi\Attributes\Items;
|
||||||
|
use OpenApi\Attributes\Property;
|
||||||
|
|
||||||
|
class Element
|
||||||
|
{
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $id;
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $pattern = '';
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $placeHolder = '';
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $displayGroup = '';
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $defaultValue = '';
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $value;
|
||||||
|
|
||||||
|
#[Property(type: 'integer')]
|
||||||
|
public null|int $minValue = null;
|
||||||
|
|
||||||
|
#[Property(type: 'integer')]
|
||||||
|
public null|int $maxValue = null;
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public null|string $help = null;
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public null|string $helpTitle = null;
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public null|string $helpLink = null;
|
||||||
|
|
||||||
|
#[Property(type: 'boolean')]
|
||||||
|
public bool $valid = true;
|
||||||
|
|
||||||
|
#[Property(type: 'boolean')]
|
||||||
|
public bool $required = false;
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $rawValue = '';
|
||||||
|
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'string'))]
|
||||||
|
public array $rawValues = [];
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $type = '';
|
||||||
|
|
||||||
|
/** @var Option[] */
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'object'))]
|
||||||
|
public array $options = [];
|
||||||
|
|
||||||
|
/** @var Element[] */
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'object'))]
|
||||||
|
public array $elements = [];
|
||||||
|
|
||||||
|
#[Property(type: 'string')]
|
||||||
|
public string $htmlType = 'input';
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'object'))]
|
||||||
|
public array $validationErrors = [];
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Dto\Output\Element;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
final class Option
|
||||||
|
{
|
||||||
|
#[OA\Property(type: 'string')]
|
||||||
|
public string $id;
|
||||||
|
|
||||||
|
#[OA\Property(type: 'string')]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
|
#[OA\Property(type: 'string')]
|
||||||
|
public string $prefix = '';
|
||||||
|
|
||||||
|
#[OA\Property(type: 'string')]
|
||||||
|
public string $suffix = '';
|
||||||
|
|
||||||
|
#[OA\Property(type: 'string')]
|
||||||
|
public string $info = '';
|
||||||
|
|
||||||
|
#[OA\Property(type: 'boolean')]
|
||||||
|
public bool $valid = true;
|
||||||
|
|
||||||
|
#[OA\Property(type: 'boolean')]
|
||||||
|
public bool $selected = false;
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Dto\Output;
|
||||||
|
|
||||||
|
use OpenApi\Attributes\Items;
|
||||||
|
use OpenApi\Attributes\Property;
|
||||||
|
use PSC\System\ContentEngineBundle\Dto\Output\Element\Element;
|
||||||
|
|
||||||
|
final class PreviewOutput
|
||||||
|
{
|
||||||
|
#[Property(type: 'boolean')]
|
||||||
|
public bool $success = true;
|
||||||
|
|
||||||
|
/** @var Element[] */
|
||||||
|
#[Property(type: 'array', items: new Items(type: 'object'))]
|
||||||
|
public array $elements = [];
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
|
|
||||||
|
class PSCSystemContentEngineBundle extends Bundle
|
||||||
|
{
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
psc_system_content_engine_api:
|
||||||
|
resource: "@PSCSystemContentEngineBundle/Api"
|
||||||
|
type: attribute
|
||||||
|
prefix: /api/content-engine
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
_defaults:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
|
||||||
|
PSC\System\ContentEngineBundle\:
|
||||||
|
resource: '../../*/*'
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PSC\System\ContentEngineBundle\Service;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PSC\Shop\EntityBundle\Entity\Cms;
|
||||||
|
|
||||||
|
class ContentResolver
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function loadXml(string $module, int $id): string
|
||||||
|
{
|
||||||
|
$entity = $this->resolveEntity($module, $id);
|
||||||
|
|
||||||
|
return match ($module) {
|
||||||
|
'cms' => $entity->getText() ?? '',
|
||||||
|
default => throw new \InvalidArgumentException("Unknown module: $module"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveXml(string $module, int $id, string $xml): void
|
||||||
|
{
|
||||||
|
$entity = $this->resolveEntity($module, $id);
|
||||||
|
|
||||||
|
match ($module) {
|
||||||
|
'cms' => $entity->setText($xml),
|
||||||
|
default => throw new \InvalidArgumentException("Unknown module: $module"),
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->entityManager->persist($entity);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveEntity(string $module, int $id): object
|
||||||
|
{
|
||||||
|
$entity = match ($module) {
|
||||||
|
'cms' => $this->entityManager->getRepository(Cms::class)->find($id),
|
||||||
|
default => throw new \InvalidArgumentException("Unknown module: $module"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!$entity) {
|
||||||
|
throw new \RuntimeException("Entity not found for module '$module' with id '$id'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,13 +35,28 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-gray-900 dark:text-gray-100 whitespace-nowrap">{{ entry.datum }}</td>
|
<td class="px-4 py-3 text-gray-900 dark:text-gray-100 whitespace-nowrap">{{ entry.datum }}</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<ul class="space-y-1">
|
<ul class="space-y-2">
|
||||||
{% for change in entry.changes %}
|
{% for change in entry.changes %}
|
||||||
<li class="flex items-start gap-2 text-gray-700 dark:text-gray-300">
|
<li class="flex items-start gap-2 text-gray-700 dark:text-gray-300">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 mt-0.5 shrink-0 text-psc-500">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 mt-0.5 shrink-0 text-psc-500">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||||
</svg>
|
</svg>
|
||||||
{{ change }}
|
{% if change is iterable %}
|
||||||
|
<div>
|
||||||
|
<span>{{ change.text }}</span>
|
||||||
|
{% if change.images is defined %}
|
||||||
|
<div class="mt-2 flex flex-wrap gap-2">
|
||||||
|
{% for image in change.images %}
|
||||||
|
<img src="{{ asset('images/changelog/' ~ image) }}" alt="{{ change.text }}" class="rounded-lg border border-gray-200 shadow-sm max-h-32 max-w-48 object-cover cursor-pointer hover:opacity-80 transition-opacity dark:border-gray-700" loading="lazy" onclick="document.getElementById('changelog-lightbox').querySelector('img').src=this.src;document.getElementById('changelog-lightbox').classList.remove('hidden')"/>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% elseif change.image is defined %}
|
||||||
|
<img src="{{ asset('images/changelog/' ~ change.image) }}" alt="{{ change.text }}" class="mt-2 rounded-lg border border-gray-200 shadow-sm max-h-32 max-w-48 object-cover cursor-pointer hover:opacity-80 transition-opacity dark:border-gray-700" loading="lazy" onclick="document.getElementById('changelog-lightbox').querySelector('img').src=this.src;document.getElementById('changelog-lightbox').classList.remove('hidden')"/>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ change }}
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
@ -53,4 +68,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="changelog-lightbox" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm cursor-pointer" onclick="this.classList.add('hidden')">
|
||||||
|
<img src="" alt="" class="max-w-[90vw] max-h-[90vh] rounded-lg shadow-2xl"/>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -14,6 +14,6 @@ class VersionTest extends WebTestCase
|
|||||||
$this->assertResponseIsSuccessful();
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
$data = json_decode($client->getResponse()->getContent(), true);
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
$this->assertSame('2.3.3', $data['release']);
|
$this->assertSame('2.3.5', $data['release']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
class ConfigTest extends ContentEngineTestCase
|
||||||
|
{
|
||||||
|
public function testLoadConfigForCms(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
$cmsId = $this->createCmsPage(
|
||||||
|
'<?xml version="1.0" encoding="utf-8"?><kalkulation><artikel><name>Test</name><option id="auflage" name="Auflage" type="Input" default="10"/></artikel></kalkulation>',
|
||||||
|
);
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/content-engine/config', [
|
||||||
|
'id' => $cmsId,
|
||||||
|
'module' => 'cms',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
self::assertArrayHasKey('json', $data);
|
||||||
|
self::assertArrayHasKey('xml', $data);
|
||||||
|
self::assertNotEmpty($data['json']);
|
||||||
|
self::assertNotEmpty($data['xml']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadConfigForCmsWithEmptyText(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
$cmsId = $this->createCmsPage('');
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/content-engine/config', [
|
||||||
|
'id' => $cmsId,
|
||||||
|
'module' => 'cms',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
self::assertArrayHasKey('json', $data);
|
||||||
|
self::assertArrayHasKey('xml', $data);
|
||||||
|
self::assertNotEmpty($data['xml']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadConfigWithInvalidModule(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/content-engine/config', [
|
||||||
|
'id' => 1,
|
||||||
|
'module' => 'invalid',
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertSame(500, $client->getResponse()->getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
use PSC\Shop\EntityBundle\Repository\ShopRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
use Tests\RefreshDatabaseTrait;
|
||||||
|
|
||||||
|
abstract class ContentEngineTestCase extends WebTestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabaseTrait;
|
||||||
|
|
||||||
|
protected function createCmsPage(string $text = ''): int
|
||||||
|
{
|
||||||
|
$shop = static::getContainer()->get(ShopRepository::class)->findOneBy(['title' => 'Printchampion']);
|
||||||
|
|
||||||
|
/** @var Connection $conn */
|
||||||
|
$conn = static::getContainer()->get(Connection::class);
|
||||||
|
|
||||||
|
$conn->insert('cms', [
|
||||||
|
'shop_id' => $shop->getUid(),
|
||||||
|
'title' => 'Test CMS Page',
|
||||||
|
'text1' => $text,
|
||||||
|
'url' => 'test-cms-' . uniqid(),
|
||||||
|
'sor' => 1,
|
||||||
|
'enable' => 1,
|
||||||
|
'pos' => 'content',
|
||||||
|
'menu' => 'main',
|
||||||
|
'notinmenu' => 0,
|
||||||
|
'modul' => 'default',
|
||||||
|
'parameter' => '',
|
||||||
|
'copy_market' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int) $conn->lastInsertId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
class DesignTest extends ContentEngineTestCase
|
||||||
|
{
|
||||||
|
public function testProcessDesign(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
$cmsId = $this->createCmsPage();
|
||||||
|
|
||||||
|
$json = json_decode('[{"uuid":"df2df718-b28e-482d-bf0c-67d246f05d32","name":"Test CMS","options":[{"id":"auflage","name":"Auflage","default":"100","dependencys":[],"placeHolder":"","required":false,"type":2}]}]', true);
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/content-engine/design', [
|
||||||
|
'id' => $cmsId,
|
||||||
|
'module' => 'cms',
|
||||||
|
'json' => $json,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
self::assertArrayHasKey('json', $data);
|
||||||
|
self::assertArrayHasKey('xml', $data);
|
||||||
|
self::assertArrayHasKey('jsonGraph', $data);
|
||||||
|
self::assertNotEmpty($data['json']);
|
||||||
|
self::assertNotEmpty($data['xml']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
class PreviewTest extends ContentEngineTestCase
|
||||||
|
{
|
||||||
|
public function testPreviewWithInputElement(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$json = json_decode('[{"uuid":"2db72c84-67a9-4fcf-99da-9307255572af","name":"CMS Preview Test","options":[{"id":"auflage","name":"Auflage","default":"100","dependencys":[{"relation":"auflage","formula":"","borders":[{"calcValue":"","formula":"$Vauflage$V*0.12","price":0,"value":"1-","dependencys":[]}]}],"placeHolder":"Placeholder","required":true,"minValue":1,"maxValue":200,"type":2}]}]', true);
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/content-engine/preview', [
|
||||||
|
'json' => $json,
|
||||||
|
'values' => ['auflage' => 100],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
self::assertArrayHasKey('elements', $data);
|
||||||
|
self::assertNotEmpty($data['elements']);
|
||||||
|
// ContentEngine preview should NOT contain pricing data
|
||||||
|
self::assertArrayNotHasKey('netto', $data);
|
||||||
|
self::assertArrayNotHasKey('brutto', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPreviewWithRowLayout(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$json = json_decode('[{"uuid":"2b6de5b2-4dac-4258-8a07-83c14552b7b8","name":"Row Test","options":[{"id":"row1","type":7,"dependencys":[],"columns":[{"id":"col1","type":8,"dependencys":[],"options":[{"id":"headline","type":6,"dependencys":[],"default":"Headline","name":"","variant":"1"}]},{"id":"col2","type":8,"dependencys":[],"options":[{"id":"text","type":6,"dependencys":[],"default":"Text","name":"","variant":"1"}]}]}]}]', true);
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/content-engine/preview', [
|
||||||
|
'json' => $json,
|
||||||
|
'values' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
self::assertArrayHasKey('elements', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPreviewWithSelectElement(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$json = json_decode('[{"uuid":"df2df718-b28e-482d-bf0c-67d246f05d32","name":"Select Test","options":[{"id":"farbe","name":"Farbe","default":"1","dependencys":[],"type":3,"options":[{"id":"1","name":"Rot","dependencys":[]},{"id":"2","name":"Blau","dependencys":[]}],"mode":"normal"}]}]', true);
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/content-engine/preview', [
|
||||||
|
'json' => $json,
|
||||||
|
'values' => ['farbe' => '1'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
self::assertArrayHasKey('elements', $data);
|
||||||
|
self::assertNotEmpty($data['elements']);
|
||||||
|
|
||||||
|
$element = $data['elements'][0];
|
||||||
|
self::assertSame('farbe', $element['id']);
|
||||||
|
self::assertNotEmpty($element['options']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
use PSC\Shop\ContactBundle\Repository\ContactRepository;
|
||||||
|
|
||||||
|
class SaveTest extends ContentEngineTestCase
|
||||||
|
{
|
||||||
|
public function testSaveXmlToCms(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
$cmsId = $this->createCmsPage('');
|
||||||
|
|
||||||
|
$userRepository = static::getContainer()->get(ContactRepository::class);
|
||||||
|
$testUser = $userRepository->loadUserByUsername('admin@shop.de');
|
||||||
|
$client->loginUser($testUser, 'api');
|
||||||
|
|
||||||
|
$newXml = '<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<kalkulation>
|
||||||
|
<artikel>
|
||||||
|
<name>Updated CMS</name>
|
||||||
|
<option id="field1" name="Feld 1" type="Input" default="test"/>
|
||||||
|
</artikel>
|
||||||
|
</kalkulation>';
|
||||||
|
|
||||||
|
$client->jsonRequest('PUT', '/api/content-engine/save', [
|
||||||
|
'id' => $cmsId,
|
||||||
|
'module' => 'cms',
|
||||||
|
'xml' => $newXml,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
self::assertTrue($data['success']);
|
||||||
|
|
||||||
|
// Verify the XML was actually persisted
|
||||||
|
/** @var Connection $conn */
|
||||||
|
$conn = static::getContainer()->get(Connection::class);
|
||||||
|
$text = $conn->fetchOne('SELECT text1 FROM cms WHERE id = ?', [$cmsId]);
|
||||||
|
self::assertStringContainsString('Updated CMS', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSaveRequiresAuthentication(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
$cmsId = $this->createCmsPage('');
|
||||||
|
|
||||||
|
$client->jsonRequest('PUT', '/api/content-engine/save', [
|
||||||
|
'id' => $cmsId,
|
||||||
|
'module' => 'cms',
|
||||||
|
'xml' => '<kalkulation/>',
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertSame(401, $client->getResponse()->getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/new/tests/PSC/System/ContentEngineBundle/Api/XmlTest.php
Normal file
42
src/new/tests/PSC/System/ContentEngineBundle/Api/XmlTest.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\PSC\System\ContentEngineBundle\Api;
|
||||||
|
|
||||||
|
class XmlTest extends ContentEngineTestCase
|
||||||
|
{
|
||||||
|
public function testProcessXml(): void
|
||||||
|
{
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
$xml = '<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<kalkulation>
|
||||||
|
<artikel>
|
||||||
|
<name>CMS Test</name>
|
||||||
|
<option id="auflage" name="Auflage" type="Input" default="10"/>
|
||||||
|
<option id="calc" type="Hidden">
|
||||||
|
<auflage>
|
||||||
|
<grenze formel="$Vauflage$V*0.5">1-</grenze>
|
||||||
|
</auflage>
|
||||||
|
</option>
|
||||||
|
</artikel>
|
||||||
|
</kalkulation>';
|
||||||
|
|
||||||
|
$cmsId = $this->createCmsPage($xml);
|
||||||
|
|
||||||
|
$client->jsonRequest('POST', '/api/content-engine/xml', [
|
||||||
|
'id' => $cmsId,
|
||||||
|
'module' => 'cms',
|
||||||
|
'xml' => $xml,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
self::assertArrayHasKey('json', $data);
|
||||||
|
self::assertArrayHasKey('xml', $data);
|
||||||
|
self::assertArrayHasKey('jsonGraph', $data);
|
||||||
|
self::assertNotEmpty($data['json']);
|
||||||
|
self::assertNotEmpty($data['xml']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -447,6 +447,7 @@ CREATE TABLE `article` (
|
|||||||
`upload_steplayouter2` int(1) DEFAULT 0,
|
`upload_steplayouter2` int(1) DEFAULT 0,
|
||||||
`upload_steplayouter2_status` int(8) DEFAULT NULL,
|
`upload_steplayouter2_status` int(8) DEFAULT NULL,
|
||||||
`sub_title` varchar(100) DEFAULT NULL,
|
`sub_title` varchar(100) DEFAULT NULL,
|
||||||
|
`confirmExternal` tinyint(1) DEFAULT 0,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `install_id_idx` (`install_id`),
|
KEY `install_id_idx` (`install_id`),
|
||||||
KEY `shop_id_idx` (`shop_id`),
|
KEY `shop_id_idx` (`shop_id`),
|
||||||
@ -1659,6 +1660,8 @@ CREATE TABLE `orderspos` (
|
|||||||
`shipping_price_mwert` float DEFAULT NULL,
|
`shipping_price_mwert` float DEFAULT NULL,
|
||||||
`ref` varchar(255) DEFAULT NULL,
|
`ref` varchar(255) DEFAULT NULL,
|
||||||
`kst` varchar(255) DEFAULT NULL,
|
`kst` varchar(255) DEFAULT NULL,
|
||||||
|
`external_approval_status` int(8) DEFAULT NULL,
|
||||||
|
`external_approval_message` text DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`,`orders_id`),
|
PRIMARY KEY (`id`,`orders_id`),
|
||||||
KEY `install_id_idx` (`install_id`),
|
KEY `install_id_idx` (`install_id`),
|
||||||
KEY `shop_id_idx` (`shop_id`),
|
KEY `shop_id_idx` (`shop_id`),
|
||||||
|
|||||||
@ -14,15 +14,18 @@ onMounted(() => {
|
|||||||
|
|
||||||
globalStore.setShopUuid(shopUuid!)
|
globalStore.setShopUuid(shopUuid!)
|
||||||
if(mode) {
|
if(mode) {
|
||||||
let x: number = parseInt(mode!)
|
const modeMap: Record<string, Mode> = { 'cms': Mode.CMS, 'news': Mode.News, 'product': Mode.Product }
|
||||||
globalStore.setMode(x as Mode)
|
const parsed = modeMap[mode.toLowerCase()] ?? parseInt(mode)
|
||||||
|
if (parsed) globalStore.setMode(parsed as Mode)
|
||||||
}
|
}
|
||||||
if (uuid) {
|
if (uuid) {
|
||||||
globalStore.setProductUuid(uuid)
|
globalStore.setProductUuid(uuid)
|
||||||
globalStore.loadConfigFromProductApi(uuid).then(data => {
|
globalStore.loadConfigFromProductApi(uuid).then(data => {
|
||||||
itemStore.parseJSON(data)
|
itemStore.parseJSON(data)
|
||||||
})
|
})
|
||||||
globalStore.loadFormulaAnalyserDataFromApi(uuid)
|
if (globalStore.isProductMode) {
|
||||||
|
globalStore.loadFormulaAnalyserDataFromApi(uuid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemStore.$subscribe((mutation, state) => {
|
itemStore.$subscribe((mutation, state) => {
|
||||||
|
|||||||
@ -118,13 +118,14 @@ function toggleAi() {
|
|||||||
<div class="flex-1 flex justify-center">
|
<div class="flex-1 flex justify-center">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="designer">{{ $t('designer') }}</TabsTrigger>
|
<TabsTrigger value="designer">{{ $t('designer') }}</TabsTrigger>
|
||||||
<TabsTrigger value="preview">{{ $t('preview') }}</TabsTrigger>
|
<TabsTrigger v-if="globalStore.isProductMode" value="preview">{{ $t('preview') }}</TabsTrigger>
|
||||||
<TabsTrigger value="xml">{{ $t('xml_view') }}</TabsTrigger>
|
<TabsTrigger value="xml">{{ $t('xml_view') }}</TabsTrigger>
|
||||||
<TabsTrigger value="paperdb">{{ $t('paperdb_view') }}</TabsTrigger>
|
<TabsTrigger v-if="globalStore.isProductMode" value="paperdb">{{ $t('paperdb_view') }}</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<Button
|
<Button
|
||||||
|
v-if="globalStore.isProductMode"
|
||||||
@click="toggleFormel"
|
@click="toggleFormel"
|
||||||
:variant="globalStore.showFormel ? 'default' : 'outline'"
|
:variant="globalStore.showFormel ? 'default' : 'outline'"
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -133,6 +134,7 @@ function toggleAi() {
|
|||||||
{{ $t('formel_view') }}
|
{{ $t('formel_view') }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
v-if="globalStore.isProductMode"
|
||||||
@click="toggleParameter"
|
@click="toggleParameter"
|
||||||
:variant="globalStore.showParameter ? 'default' : 'outline'"
|
:variant="globalStore.showParameter ? 'default' : 'outline'"
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -157,13 +159,13 @@ function toggleAi() {
|
|||||||
<Preview v-else />
|
<Preview v-else />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="preview" class="flex-1 overflow-y-auto min-h-0">
|
<TabsContent v-if="globalStore.isProductMode" value="preview" class="flex-1 overflow-y-auto min-h-0">
|
||||||
<FormulaVisualizer />
|
<FormulaVisualizer />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="xml" class="flex-1 overflow-y-auto min-h-0">
|
<TabsContent value="xml" class="flex-1 overflow-y-auto min-h-0">
|
||||||
<XmlView />
|
<XmlView />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="paperdb" class="flex-1 overflow-y-auto min-h-0">
|
<TabsContent v-if="globalStore.isProductMode" value="paperdb" class="flex-1 overflow-y-auto min-h-0">
|
||||||
<PaperDBView />
|
<PaperDBView />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@ -187,6 +189,7 @@ function toggleAi() {
|
|||||||
<div class="flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-slate-50 shrink-0">
|
<div class="flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-slate-50 shrink-0">
|
||||||
<span class="text-xs font-medium text-gray-500 uppercase tracking-wide">Code</span>
|
<span class="text-xs font-medium text-gray-500 uppercase tracking-wide">Code</span>
|
||||||
<Button
|
<Button
|
||||||
|
v-if="globalStore.isProductMode"
|
||||||
@click="globalStore.syncFormulasAndParameter()"
|
@click="globalStore.syncFormulasAndParameter()"
|
||||||
:disabled="globalStore.syncing"
|
:disabled="globalStore.syncing"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -47,10 +47,10 @@ onMounted(() => {
|
|||||||
<div v-if="!isLoading && previewData">
|
<div v-if="!isLoading && previewData">
|
||||||
<div class="overflow-auto h-full">
|
<div class="overflow-auto h-full">
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="w-3/5 flex flex-col gap-2">
|
<div :class="globalStore.isProductMode ? 'w-3/5' : 'w-full'" class="flex flex-col gap-2">
|
||||||
<RenderElements :items="items" @update:value="handleUpdate"/>
|
<RenderElements :items="items" @update:value="handleUpdate"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/5 pl-6">
|
<div v-if="globalStore.isProductMode" class="w-2/5 pl-6">
|
||||||
<PriceDisplay :price-data="price" />
|
<PriceDisplay :price-data="price" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -190,6 +190,58 @@ export const sendAiMessage = async (
|
|||||||
// returns { reply: string, xml?: string, formulas?: string, parameter?: string }
|
// returns { reply: string, xml?: string, formulas?: string, parameter?: string }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ContentEngine API (for CMS/content mode — no pricing, formulas, paper)
|
||||||
|
|
||||||
|
export const loadContentConfig = async (id: number, module: string) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('api/content-engine/config', { json: { id, module } });
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading content config:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveContentDesign = async (id: number, module: string, json: object[]) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('api/content-engine/design', { json: { id, module, json } });
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving content design:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveContentXml = async (id: number, module: string, xml: string) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('api/content-engine/xml', { json: { id, module, xml } });
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving content XML:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveContentToApi = async (id: number, module: string, xml: string) => {
|
||||||
|
try {
|
||||||
|
const response = await api.put('api/content-engine/save', { json: { id, module, xml } });
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving content:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchContentPreview = async (json: object[], values?: Record<string, any>) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('api/content-engine/preview', { json: { json, values } });
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching content preview:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchMediaUrl = async (uuid: string) => {
|
export const fetchMediaUrl = async (uuid: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`api/media/${uuid}`);
|
const response = await api.get(`api/media/${uuid}`);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import BaseElement from '../model/BaseElement'
|
import BaseElement from '../model/BaseElement'
|
||||||
import Mode from '../model/Mode'
|
import Mode from '../model/Mode'
|
||||||
import type { PreviewResponse } from '../model/preview/types';
|
import type { PreviewResponse } from '../model/preview/types';
|
||||||
import { saveProductToApi, saveFomulasAndParameterToApi, loadJsonFromApi, loadPriceFromApi, savePaperContainerToApi, saveDesignToApi, saveXmlToApi, fetchPreview } from '../lib/api'
|
import { saveProductToApi, saveFomulasAndParameterToApi, loadJsonFromApi, loadPriceFromApi, savePaperContainerToApi, saveDesignToApi, saveXmlToApi, fetchPreview, loadContentConfig, saveContentDesign, saveContentXml, saveContentToApi, fetchContentPreview } from '../lib/api'
|
||||||
import { useItemStore } from './Items'
|
import { useItemStore } from './Items'
|
||||||
|
|
||||||
export const useGlobalStore = defineStore('global', {
|
export const useGlobalStore = defineStore('global', {
|
||||||
@ -50,6 +50,10 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
getFormulaData: (state) => state.formulaData,
|
getFormulaData: (state) => state.formulaData,
|
||||||
getFormulaError: (state) => state.formulaError,
|
getFormulaError: (state) => state.formulaError,
|
||||||
getPreviewData: (state) => state.previewData as PreviewResponse | null,
|
getPreviewData: (state) => state.previewData as PreviewResponse | null,
|
||||||
|
isProductMode: (state) => state.mode === Mode.Product,
|
||||||
|
isContentMode: (state) => state.mode === Mode.CMS || state.mode === Mode.News,
|
||||||
|
contentModule: (state) => state.mode === Mode.CMS ? 'cms' : state.mode === Mode.News ? 'news' : '',
|
||||||
|
contentId: (state) => parseInt(state.productUuid) || 0,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setXml(value: string) {
|
setXml(value: string) {
|
||||||
@ -120,10 +124,13 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
this.formulas = snapshot.formulas
|
this.formulas = snapshot.formulas
|
||||||
this.parameter = snapshot.parameter
|
this.parameter = snapshot.parameter
|
||||||
const itemStore = useItemStore()
|
const itemStore = useItemStore()
|
||||||
return saveXmlToApi(this.productUuid, snapshot.xml).then((result: any) => {
|
const xmlPromise = this.isContentMode
|
||||||
|
? saveContentXml(this.contentId, this.contentModule, snapshot.xml)
|
||||||
|
: saveXmlToApi(this.productUuid, snapshot.xml)
|
||||||
|
return xmlPromise.then((result: any) => {
|
||||||
this.setXML(result.xml)
|
this.setXML(result.xml)
|
||||||
this.setJSON(result.json)
|
this.setJSON(result.json)
|
||||||
this.formulaData = JSON.parse(result.jsonGraph)
|
if (result.jsonGraph) this.formulaData = JSON.parse(result.jsonGraph)
|
||||||
itemStore.parseJSON(result.json)
|
itemStore.parseJSON(result.json)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -133,10 +140,12 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
const itemStore = useItemStore()
|
const itemStore = useItemStore()
|
||||||
this.syncing = true
|
this.syncing = true
|
||||||
try {
|
try {
|
||||||
const syncResult: any = await saveXmlToApi(this.productUuid, result.xml)
|
const syncResult: any = this.isContentMode
|
||||||
|
? await saveContentXml(this.contentId, this.contentModule, result.xml)
|
||||||
|
: await saveXmlToApi(this.productUuid, result.xml)
|
||||||
this.setXML(syncResult.xml)
|
this.setXML(syncResult.xml)
|
||||||
this.setJSON(syncResult.json)
|
this.setJSON(syncResult.json)
|
||||||
this.formulaData = JSON.parse(syncResult.jsonGraph)
|
if (syncResult.jsonGraph) this.formulaData = JSON.parse(syncResult.jsonGraph)
|
||||||
itemStore.parseJSON(syncResult.json)
|
itemStore.parseJSON(syncResult.json)
|
||||||
} finally {
|
} finally {
|
||||||
this.syncing = false
|
this.syncing = false
|
||||||
@ -156,6 +165,12 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
this.shopUuid = value
|
this.shopUuid = value
|
||||||
},
|
},
|
||||||
async loadConfigFromProductApi(uuid: string) {
|
async loadConfigFromProductApi(uuid: string) {
|
||||||
|
if (this.isContentMode) {
|
||||||
|
const data: any = await loadContentConfig(this.contentId, this.contentModule)
|
||||||
|
this.json = data.json
|
||||||
|
this.xml = data.xml
|
||||||
|
return data.json
|
||||||
|
}
|
||||||
const data: any = await loadJsonFromApi(uuid)
|
const data: any = await loadJsonFromApi(uuid)
|
||||||
this.json = data.json
|
this.json = data.json
|
||||||
this.xml = data.xml
|
this.xml = data.xml
|
||||||
@ -192,25 +207,34 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
this.json = json
|
this.json = json
|
||||||
},
|
},
|
||||||
saveDesign(json: object[]) {
|
saveDesign(json: object[]) {
|
||||||
saveDesignToApi(this.productUuid, this.shopUuid, json).then((result: any) => {
|
const designPromise = this.isContentMode
|
||||||
|
? saveContentDesign(this.contentId, this.contentModule, json)
|
||||||
|
: saveDesignToApi(this.productUuid, this.shopUuid, json)
|
||||||
|
designPromise.then((result: any) => {
|
||||||
this.setXML(result.xml)
|
this.setXML(result.xml)
|
||||||
this.setJSON(result.json)
|
this.setJSON(result.json)
|
||||||
this.formulaData = JSON.parse(result.jsonGraph);
|
if (result.jsonGraph) this.formulaData = JSON.parse(result.jsonGraph);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
manualSave() {
|
manualSave() {
|
||||||
this.saving = true
|
this.saving = true
|
||||||
saveProductToApi(this.productUuid, this.xml).then((result: any) => {
|
const savePromise = this.isContentMode
|
||||||
|
? saveContentToApi(this.contentId, this.contentModule, this.xml)
|
||||||
|
: saveProductToApi(this.productUuid, this.xml)
|
||||||
|
savePromise.then((result: any) => {
|
||||||
this.saving = false
|
this.saving = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
manualSync() {
|
manualSync() {
|
||||||
this.syncing = true
|
this.syncing = true
|
||||||
if (this.currentTab == 'xml') {
|
if (this.currentTab == 'xml') {
|
||||||
saveXmlToApi(this.productUuid, this.xml).then((result: any) => {
|
const xmlPromise = this.isContentMode
|
||||||
|
? saveContentXml(this.contentId, this.contentModule, this.xml)
|
||||||
|
: saveXmlToApi(this.productUuid, this.xml)
|
||||||
|
xmlPromise.then((result: any) => {
|
||||||
this.setXML(result.xml)
|
this.setXML(result.xml)
|
||||||
this.setJSON(result.json)
|
this.setJSON(result.json)
|
||||||
this.formulaData = JSON.parse(result.jsonGraph);
|
if (result.jsonGraph) this.formulaData = JSON.parse(result.jsonGraph);
|
||||||
this.syncing = false
|
this.syncing = false
|
||||||
const itemStore = useItemStore()
|
const itemStore = useItemStore()
|
||||||
itemStore.parseJSON(result.json)
|
itemStore.parseJSON(result.json)
|
||||||
@ -236,7 +260,9 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
async loadPreview(json: object[], values?: Record<string, any>) {
|
async loadPreview(json: object[], values?: Record<string, any>) {
|
||||||
this.previewError = '';
|
this.previewError = '';
|
||||||
try {
|
try {
|
||||||
const response: any = await fetchPreview(this.shopUuid, json, values);
|
const response: any = this.isContentMode
|
||||||
|
? await fetchContentPreview(json, values)
|
||||||
|
: await fetchPreview(this.shopUuid, json, values);
|
||||||
this.previewData = response;
|
this.previewData = response;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.previewError = `Failed to load preview data: ${e.message}`;
|
this.previewError = `Failed to load preview data: ${e.message}`;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Plugin\Custom\PSC\FormBuilder;
|
namespace Plugin\Custom\PSC\FormBuilder;
|
||||||
|
|
||||||
use PSC\System\PluginBundle\Plugin\Base;
|
use PSC\System\PluginBundle\Plugin\Base;
|
||||||
|
|
||||||
class Plugin extends Base implements \PSC\System\PluginBundle\Interfaces\Plugin {
|
class Plugin extends Base implements \PSC\System\PluginBundle\Interfaces\Plugin
|
||||||
|
{
|
||||||
protected $name = 'FormBuilder';
|
protected $name = 'ContentBuilder';
|
||||||
|
|
||||||
public function getType()
|
public function getType()
|
||||||
{
|
{
|
||||||
@ -14,7 +15,7 @@ class Plugin extends Base implements \PSC\System\PluginBundle\Interfaces\Plugin
|
|||||||
|
|
||||||
public function getDescription()
|
public function getDescription()
|
||||||
{
|
{
|
||||||
return 'Formulare Kalkulation und CMS Builder';
|
return 'Content, Formulare, Kalkulation-Builder';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getVersion()
|
public function getVersion()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1254,6 +1254,14 @@ html {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-h-32{
|
||||||
|
max-height: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-h-\[90vh\]{
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
.min-h-\[2\.25rem\]{
|
.min-h-\[2\.25rem\]{
|
||||||
min-height: 2.25rem;
|
min-height: 2.25rem;
|
||||||
}
|
}
|
||||||
@ -1407,6 +1415,10 @@ html {
|
|||||||
max-width: 75%;
|
max-width: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-w-\[90vw\]{
|
||||||
|
max-width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
.max-w-md{
|
.max-w-md{
|
||||||
max-width: 28rem;
|
max-width: 28rem;
|
||||||
}
|
}
|
||||||
@ -1953,6 +1965,10 @@ html {
|
|||||||
border-color: rgb(250 204 21 / var(--tw-border-opacity));
|
border-color: rgb(250 204 21 / var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-black\/70{
|
||||||
|
background-color: rgb(0 0 0 / 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-blue-100{
|
.bg-blue-100{
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
|
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
|
||||||
@ -2675,6 +2691,12 @@ html {
|
|||||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.backdrop-blur-sm{
|
||||||
|
--tw-backdrop-blur: blur(4px);
|
||||||
|
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||||
|
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||||
|
}
|
||||||
|
|
||||||
.backdrop-blur-xl{
|
.backdrop-blur-xl{
|
||||||
--tw-backdrop-blur: blur(24px);
|
--tw-backdrop-blur: blur(24px);
|
||||||
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||||
@ -4130,6 +4152,10 @@ html {
|
|||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:opacity-80:hover{
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:shadow-lg:hover{
|
.hover\:shadow-lg:hover{
|
||||||
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
info:
|
info:
|
||||||
datum: 20.03.2026
|
datum: 26.03.2026
|
||||||
release: 2.3.5
|
release: 2.3.5
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
- version: 2.3.5
|
- version: 2.3.5
|
||||||
datum: 20.03.2026
|
datum: 26.03.2026
|
||||||
changes:
|
changes:
|
||||||
|
- text: "CMS ContentBuilder Plugin kann aktiviert. (Custom Plugin und das Storefront Template muss die Ausgabe unterstützen)"
|
||||||
|
images:
|
||||||
|
- "screen1.png"
|
||||||
|
- "screen2.png"
|
||||||
|
- "CMS Wysiwyg respektieren von h,ul tags"
|
||||||
- "Löschen von Kunden wenn kein Afträge da."
|
- "Löschen von Kunden wenn kein Afträge da."
|
||||||
- version: 2.3.4
|
- version: 2.3.4
|
||||||
datum: 19.03.2026
|
datum: 19.03.2026
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user