diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..c206823
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,2 @@
+@netlogix-internal/neos
+@netlogix-internal/ecommerce
diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml
new file mode 100644
index 0000000..2f63c2a
--- /dev/null
+++ b/.github/workflows/release-tag.yml
@@ -0,0 +1,30 @@
+name: Release Tag on Merge to Main
+
+on:
+ pull_request:
+ types: [closed]
+ branches: [main]
+
+jobs:
+ tag:
+ if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+ with:
+ fetch-tags: true
+
+ - name: Extract version from composer.json
+ id: version
+ run: |
+ VERSION=$(jq -r '.version' composer.json)
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+
+ - name: Create Git tag
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git tag -a "${{ steps.version.outputs.version }}" -m "Release ${{ steps.version.outputs.version }}"
+ git push origin "${{ steps.version.outputs.version }}"
diff --git a/.github/workflows/require-version-raised.yml b/.github/workflows/require-version-raised.yml
new file mode 100644
index 0000000..78ffef1
--- /dev/null
+++ b/.github/workflows/require-version-raised.yml
@@ -0,0 +1,44 @@
+name: Require Version Raise
+
+on:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ require-version-bump:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-tags: true
+
+ - name: Read head version (PR branch)
+ id: head_version
+ run: |
+ HEAD_VERSION=$(jq -r '.version' composer.json)
+ echo "value=$HEAD_VERSION" >> $GITHUB_OUTPUT
+
+ - name: Read base version (main branch)
+ id: base_version
+ run: |
+ git fetch origin main
+ git checkout origin/main -- composer.json
+ BASE_VERSION=$(jq -r '.version' composer.json)
+ echo "value=$BASE_VERSION" >> $GITHUB_OUTPUT
+
+ - name: Compare versions
+ run: |
+ BASE="${{ steps.base_version.outputs.value }}"
+ HEAD="${{ steps.head_version.outputs.value }}"
+
+ echo "Base version: $BASE"
+ echo "PR version: $HEAD"
+
+ if [ "$BASE" = "$HEAD" ]; then
+ echo "❌ ERROR: composer.json version must be updated in this PR."
+ exit 1
+ else
+ echo "✅ Version changed — OK."
+ fi
diff --git a/.github/workflows/upmerge.yml b/.github/workflows/upmerge.yml
new file mode 100644
index 0000000..c8e62bd
--- /dev/null
+++ b/.github/workflows/upmerge.yml
@@ -0,0 +1,25 @@
+name: Create Upmerge PR
+
+on:
+ push:
+ branches:
+ - develop
+
+jobs:
+ upmerge:
+ runs-on: ubuntu-latest
+ env:
+ GH_TOKEN: ${{ github.token }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+
+ - run: |
+ OPEN_PR=$(gh pr list --head=develop --base=main --json=url)
+
+ if [ "$OPEN_PR" != "[]" ]; then
+ echo "Open PRs found, skipping..."
+ exit 0
+ fi
+
+ gh pr create --base=main --head=develop --title="UPMERGE: develop -> main" --body="Merge develop to main"
diff --git a/composer.json b/composer.json
index e733ba7..15b7ac4 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,7 @@
{
"name": "netlogix/neos-content",
"description": "This plugin enables Shopware templates to be designed with an Enterprise CMS.",
- "version": "0.1.24",
+ "version": "0.1.26",
"type": "shopware-platform-plugin",
"license": "MIT",
"autoload": {
diff --git a/src/Core/Content/Admin/AbstractUpdatePagesRoute.php b/src/Core/Content/Admin/AbstractUpdatePagesRoute.php
index c1434de..c183344 100644
--- a/src/Core/Content/Admin/AbstractUpdatePagesRoute.php
+++ b/src/Core/Content/Admin/AbstractUpdatePagesRoute.php
@@ -12,5 +12,5 @@ abstract class AbstractUpdatePagesRoute
{
abstract public function getDecorated(): AbstractUpdatePagesRoute;
- abstract public function load(): Response;
+ abstract public function load(Request $request, Context $context): Response;
}
diff --git a/src/Core/Content/Admin/UpdateNeosPagesRoute.php b/src/Core/Content/Admin/UpdateNeosPagesRoute.php
index 8551e95..c4e5858 100644
--- a/src/Core/Content/Admin/UpdateNeosPagesRoute.php
+++ b/src/Core/Content/Admin/UpdateNeosPagesRoute.php
@@ -27,21 +27,24 @@ public function getDecorated(): AbstractUpdatePagesRoute
}
#[Route(path: '/api/_action/neos/update-neos-pages', name: 'api.neos.update-neos-pages', methods: ['POST'])]
- public function load(): Response
+ public function load(Request $request, Context $context): Response
{
- $context = Context::createDefaultContext();
+ $content = json_decode(json: $request->getContent(), associative: true) ?? [];
+ // If nodes are provided in the request, only process those
+ if (!empty($content['updatedNodes'])) {
+ $this->neosLayoutPageService->processProvidedNodes($content['updatedNodes'], $context);
+ return new JsonResponse(['status' => 'Provided Neos layout pages processed successfully.'], Response::HTTP_OK);
+ }
+
try {
- $neosPages = $this->neosLayoutPageService->getNeosLayoutPages(
- explode('|', NeosLayoutPageService::AVAILABLE_FILTER_PAGE_TYPES)
- );
+ $neosPages = $this->neosLayoutPageService->getNeosCmsPageTemplates();
} catch (GuzzleException $e) {
$this->neosLayoutPageService->createNotification($context);
- throw new Exception('Failed to retrieve Neos layout pages: ' . $e->getMessage(), 1751381726, $e);
+ throw new Exception('Failed to retrieve Neos templates: ' . $e->getMessage(), 1751381726, $e);
}
- $this->neosLayoutPageService->createMissingNeosCmsPages($neosPages, $context);
$this->neosLayoutPageService->updateNeosCmsPages($neosPages, $context);
- $this->neosLayoutPageService->removeCmsPagesWithInvalidNodeIdentifiers($neosPages, $context);
+ $this->neosLayoutPageService->removeObsoleteCmsPages($neosPages, $context);
return new JsonResponse(['status' => 'Neos layout pages updated successfully.'], Response::HTTP_OK);
diff --git a/src/Core/Content/Cms/Aggregate/CmsBlock/NeosCmsBlockCollection.php b/src/Core/Content/Cms/Aggregate/CmsBlock/NeosCmsBlockCollection.php
new file mode 100644
index 0000000..49b457e
--- /dev/null
+++ b/src/Core/Content/Cms/Aggregate/CmsBlock/NeosCmsBlockCollection.php
@@ -0,0 +1,12 @@
+addFlags(new Required(), new PrimaryKey())),
(new DateField('created_at', 'createdAt')),
(new DateField('updated_at', 'updatedAt')),
- (new StringField('node_identifier', 'nodeIdentifier')),
+ (new BoolField('neos_connection', 'neosConnection')),
(new FkField('cms_page_id', 'cmsPageId', CmsPageDefinition::class))->addFlags(new Required()),
(new ReferenceVersionField(CmsPageDefinition::class, 'cms_page_version_id')),
(new OneToOneAssociationField('cmsPage', 'cms_page_id', 'id', CmsPageDefinition::class, false)),
diff --git a/src/Core/Content/NeosNode/NeosNodeEntity.php b/src/Core/Content/NeosNode/NeosNodeEntity.php
index 3233f8d..8912535 100644
--- a/src/Core/Content/NeosNode/NeosNodeEntity.php
+++ b/src/Core/Content/NeosNode/NeosNodeEntity.php
@@ -19,16 +19,16 @@ class NeosNodeEntity extends Entity
protected string $cmsPageVersionId;
- protected ?string $nodeIdentifier;
+ protected bool $neosConnection = false;
- public function getNodeIdentifier(): ?string
+ public function getNeosConnection(): bool
{
- return $this->nodeIdentifier;
+ return $this->neosConnection;
}
- public function nodeIdentifier(?string $nodeIdentifier): void
+ public function neosConnection(bool $neosConnection): void
{
- $this->nodeIdentifier = $nodeIdentifier;
+ $this->neosConnection = $neosConnection;
}
public function getCmsPage(): CmsPageEntity
diff --git a/src/Error/CanNotDeleteDefaultLayoutPageException.php b/src/Error/CanNotDeleteDefaultLayoutPageException.php
index e61cd63..029bbfc 100644
--- a/src/Error/CanNotDeleteDefaultLayoutPageException.php
+++ b/src/Error/CanNotDeleteDefaultLayoutPageException.php
@@ -6,7 +6,7 @@
use Shopware\Core\Content\Cms\CmsPageEntity;
-class CanNotDeleteDefaultLayoutPageException extends \Exception
+class CanNotDeleteDefaultLayoutPageException extends \Exception implements NeosExceptionInterface
{
public function __construct(CmsPageEntity $cmsPageEntity, int $code = 1753085155, ?\Throwable $previous = null)
{
diff --git a/src/Error/FaultyNeosSectionDataException.php b/src/Error/FaultyNeosSectionDataException.php
new file mode 100644
index 0000000..40c2623
--- /dev/null
+++ b/src/Error/FaultyNeosSectionDataException.php
@@ -0,0 +1,16 @@
+identifier => new TreeItem(
+ $this->createCategoryEntity($page),
+ empty($page->children) ? [] : iterator_to_array($this->create($page->children))
+ );
+ } catch (\Throwable $exception) {
+ dd($page, $exception);
+ }
+ }
+ }
+
+ private function createCategoryEntity(NeosPageDTO $page): CategoryEntity
+ {
+ /**
+ * Data needed
+ * $categoryName
+ * translations for the name and possible other fields
+ */
+
+ //TODO figure out child count and visible child count and level
+
+ $category = new SalesChannelCategoryEntity();
+ $category->setId($page->identifier);
+ $category->setName($page->label);
+ $category->setType('neos-entrypoint');
+ $category->setSeoUrl(
+ sprintf(
+ '%s/%s#',
+ SeoUrlPlaceholderHandler::DOMAIN_PLACEHOLDER,
+ trim($page->path, '/')
+ )
+ );
+ $category->setTranslated([
+ "breadcrumb" => [],
+ "name" => $category->getName(),
+ "customFields" => [],
+ "slotConfig" => [],
+ "linkType" => 'link',
+ "internalLink" => null,
+ "externalLink" => null,
+ "linkNewTab" => true,
+ "description" => null,
+ "metaTitle" => null,
+ "metaDescription" => null,
+ "keywords" => null,
+ ]
+ );
+
+ return $category;
+ }
+}
diff --git a/src/Subscriber/CmsPageLoadedSubscriber.php b/src/Listener/CmsPageLoadedListener.php
similarity index 67%
rename from src/Subscriber/CmsPageLoadedSubscriber.php
rename to src/Listener/CmsPageLoadedListener.php
index 3432ad5..8d0c8e1 100644
--- a/src/Subscriber/CmsPageLoadedSubscriber.php
+++ b/src/Listener/CmsPageLoadedListener.php
@@ -2,11 +2,10 @@
declare(strict_types=1);
-namespace nlxNeosContent\Subscriber;
+namespace nlxNeosContent\Listener;
use nlxNeosContent\Core\Content\NeosNode\NeosNodeEntity;
use nlxNeosContent\Error\MissingCmsPageEntityException;
-use nlxNeosContent\Resolver\NeosDimensionResolver;
use nlxNeosContent\Service\ContentExchangeService;
use nlxNeosContent\Service\ResolverContextService;
use Shopware\Core\Content\Category\CategoryDefinition;
@@ -17,18 +16,16 @@
use Shopware\Core\Content\Cms\DataResolver\FieldConfigCollection;
use Shopware\Core\Content\Cms\Events\CmsPageLoadedEvent;
use Shopware\Core\Content\Product\ProductDefinition;
-use Shopware\Core\Framework\Struct\Struct;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\Request;
#[AsEventListener]
-class CmsPageLoadedSubscriber
+class CmsPageLoadedListener
{
public function __construct(
private readonly ContentExchangeService $contentExchangeService,
private readonly ResolverContextService $resolverContextService,
- private readonly NeosDimensionResolver $neosDimensionResolver,
) {
}
@@ -45,54 +42,44 @@ public function __invoke(CmsPageLoadedEvent $cmsPageLoadedEvent): void
if (!$cmsPageEntity->hasExtension('nlxNeosNode')) {
return;
}
+ /** @var NeosNodeEntity $neosNode */
$neosNode = $cmsPageEntity->getExtension('nlxNeosNode');
- $hasNeosNodeIdentifier = $this->neosNodeHasNodeIdentifier($neosNode);
-
- if (!$hasNeosNodeIdentifier) {
- //Nothing to do continue with default shopware logic
+ if (!$neosNode->getNeosConnection()) {
return;
}
- $dimensions = $this->neosDimensionResolver->resolveDimensions(
- $cmsPageLoadedEvent->getSalesChannelContext(),
- $cmsPageLoadedEvent->getRequest()
- );
-
- match ($cmsPageEntity->getType()) {
- 'product_list' => $newCmsSections = $this->getNewListingPageSections(
+ $newCmsSections = match ($cmsPageEntity->getType()) {
+ 'product_list' => $this->getNewListingPageSections(
$cmsPageLoadedEvent->getSalesChannelContext()->getSalesChannel()->getNavigationCategoryId(),
$cmsPageLoadedEvent->getSalesChannelContext(),
$cmsPageLoadedEvent->getRequest(),
- $neosNode->getVars()['nodeIdentifier'],
- $dimensions
+ $cmsPageEntity,
),
- 'product_detail' => $newCmsSections = $this->getNewDetailPageBlocks(
+ 'product_detail' => $this->getNewDetailPageBlocks(
$cmsPageLoadedEvent->getRequest()->attributes->get('productId'),
$cmsPageLoadedEvent->getSalesChannelContext(),
$cmsPageLoadedEvent->getRequest(),
- $neosNode->getVars()['nodeIdentifier'],
- $dimensions
+ $cmsPageEntity,
),
- 'landingpage' => $newCmsSections = $this->getNewLandingPageBlocks(
- $neosNode->getVars()['nodeIdentifier'],
- $dimensions
+ 'landingpage' => $this->getNewLandingPageBlocks(
+ $cmsPageEntity,
+ $cmsPageLoadedEvent->getSalesChannelContext()
),
- 'page' => $newCmsSections = $this->getNewShopPageBlocks(
- $neosNode->getVars()['nodeIdentifier'],
- $dimensions
+ 'page' => $this->getNewShopPageBlocks(
+ $cmsPageEntity,
+ $cmsPageLoadedEvent->getSalesChannelContext()
)
};
- $this->replaceCmsSections($cmsPageEntity, $newCmsSections);
+ $cmsPageEntity->setSections($newCmsSections);
}
private function getNewListingPageSections(
string $categoryId,
SalesChannelContext $salesChannelContext,
Request $request,
- string $nodeIdentifier,
- Struct $dimensions
+ CmsPageEntity $cmsPage,
): CmsSectionCollection {
$resolverContext = $this->resolverContextService->getResolverContextForEntityNameAndId(
CategoryDefinition::ENTITY_NAME,
@@ -102,8 +89,9 @@ private function getNewListingPageSections(
);
$alternativeCmsSectionsFromNeos = $this->contentExchangeService->getAlternativeCmsSectionsFromNeos(
- $nodeIdentifier,
- $dimensions
+ $cmsPage,
+ $salesChannelContext->getLanguageId(),
+ $salesChannelContext->getSalesChannelId()
);
$this->contentExchangeService->loadSlotData($alternativeCmsSectionsFromNeos->getBlocks(), $resolverContext);
@@ -112,21 +100,21 @@ private function getNewListingPageSections(
private function getNewDetailPageBlocks(
string $productId,
- SalesChannelContext $salesChannelContext,
+ SalesChannelContext $context,
Request $request,
- string $nodeIdentifier,
- Struct $dimensions
+ CmsPageEntity $cmsPage,
): CmsSectionCollection {
$resolverContext = $this->resolverContextService->getResolverContextForEntityNameAndId(
ProductDefinition::ENTITY_NAME,
$productId,
- $salesChannelContext,
+ $context,
$request
);
$alternativeCmsBlocksFromNeos = $this->contentExchangeService->getAlternativeCmsSectionsFromNeos(
- $nodeIdentifier,
- $dimensions
+ $cmsPage,
+ $context->getLanguageId(),
+ $context->getSalesChannelId()
);
/** @var CmsBlockEntity $cmsBlock */
@@ -151,40 +139,24 @@ private function getNewDetailPageBlocks(
}
private function getNewLandingPageBlocks(
- string $nodeIdentifier,
- Struct $dimensions
+ CmsPageEntity $cmsPageEntity,
+ SalesChannelContext $context
): CmsSectionCollection {
return $this->contentExchangeService->getAlternativeCmsSectionsFromNeos(
- $nodeIdentifier,
- $dimensions
+ $cmsPageEntity,
+ $context->getLanguageId(),
+ $context->getSalesChannelId()
);
}
private function getNewShopPageBlocks(
- string $nodeIdentifier,
- Struct $dimensions
+ CmsPageEntity $cmsPageEntity,
+ SalesChannelContext $context
): CmsSectionCollection {
return $this->contentExchangeService->getAlternativeCmsSectionsFromNeos(
- $nodeIdentifier,
- $dimensions
+ $cmsPageEntity,
+ $context->getLanguageId(),
+ $context->getSalesChannelId()
);
}
-
- private function replaceCmsSections(
- CmsPageEntity $cmsPageEntity,
- CmsSectionCollection $newCmsSections,
- ): void {
- $cmsPageEntity->setSections($newCmsSections);
- }
-
- private function neosNodeHasNodeIdentifier(NeosNodeEntity $neosNode): bool
- {
- $nodeIdentifier = $neosNode->getNodeIdentifier();
-
- if (!$nodeIdentifier) {
- return false;
- }
-
- return true;
- }
}
diff --git a/src/Subscriber/CorsListener.php b/src/Listener/CorsListener.php
similarity index 93%
rename from src/Subscriber/CorsListener.php
rename to src/Listener/CorsListener.php
index 77e758f..7a5aefe 100644
--- a/src/Subscriber/CorsListener.php
+++ b/src/Listener/CorsListener.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace nlxNeosContent\Subscriber;
+namespace nlxNeosContent\Listener;
use nlxNeosContent\Service\ConfigService;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
@@ -13,9 +13,9 @@
#[AsEventListener(method: 'onKernelRequest', priority: -10000)]
#[AsEventListener(method: 'onKernelResponse', priority: -10000)]
-class CorsListener
+readonly class CorsListener
{
- function __construct(private readonly ConfigService $configService)
+ function __construct(private ConfigService $configService)
{
}
diff --git a/src/Listener/NavigationLoadedListener.php b/src/Listener/NavigationLoadedListener.php
new file mode 100644
index 0000000..5041074
--- /dev/null
+++ b/src/Listener/NavigationLoadedListener.php
@@ -0,0 +1,34 @@
+getNavigation();
+ $tree = $navigation->getTree();
+
+ $pages = $this->neosPageTreeLoader->load(
+ $navigationLoadedEvent->getSalesChannelContext()->getSalesChannelId(),
+ $navigationLoadedEvent->getSalesChannelContext()->getLanguageId(),
+
+ );
+ $pages = iterator_to_array($this->neosPageTreeItemFactory->create($pages));
+ $navigation->setTree(array_merge($tree, $pages));
+ }
+}
diff --git a/src/Listener/NeosExceptionListener.php b/src/Listener/NeosExceptionListener.php
new file mode 100644
index 0000000..155ee66
--- /dev/null
+++ b/src/Listener/NeosExceptionListener.php
@@ -0,0 +1,35 @@
+getRequest()->attributes->get(PlatformRequest::ATTRIBUTE_ROUTE_SCOPE, []);
+
+ if (!\in_array(StorefrontRouteScope::ID, $scopes, true)) {
+ return;
+ }
+
+ if(!$event->getThrowable() instanceof NeosExceptionInterface) {
+ return;
+ }
+
+ throw new ServiceUnavailableHttpException(
+ 30,
+ 'Neos CMS is currently unavailable.',
+ $event->getThrowable()
+ );
+ }
+}
diff --git a/src/Subscriber/SalesChannelProcessCriteriaSubscriber.php b/src/Listener/SalesChannelProcessCriteriaListener.php
similarity index 91%
rename from src/Subscriber/SalesChannelProcessCriteriaSubscriber.php
rename to src/Listener/SalesChannelProcessCriteriaListener.php
index 5f5e338..a3457c6 100644
--- a/src/Subscriber/SalesChannelProcessCriteriaSubscriber.php
+++ b/src/Listener/SalesChannelProcessCriteriaListener.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace nlxNeosContent\Subscriber;
+namespace nlxNeosContent\Listener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Content\Product\SalesChannel\ProductAvailableFilter;
@@ -10,7 +10,7 @@
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag(name: 'kernel.event_subscriber')]
-class SalesChannelProcessCriteriaSubscriber implements EventSubscriberInterface
+class SalesChannelProcessCriteriaListener implements EventSubscriberInterface
{
public function __construct()
{
diff --git a/src/Migration/Migration1768465060ChangeToNeosConnectionExtension.php b/src/Migration/Migration1768465060ChangeToNeosConnectionExtension.php
new file mode 100644
index 0000000..add6a5d
--- /dev/null
+++ b/src/Migration/Migration1768465060ChangeToNeosConnectionExtension.php
@@ -0,0 +1,32 @@
+executeStatement(
+ $sql
+ );
+ }
+
+ public function updateDestructive(Connection $connection): void
+ {
+ // Destructive changes (if needed)
+ }
+}
diff --git a/src/Neos/DTO/NeosPageCollection.php b/src/Neos/DTO/NeosPageCollection.php
new file mode 100644
index 0000000..b17aaf8
--- /dev/null
+++ b/src/Neos/DTO/NeosPageCollection.php
@@ -0,0 +1,17 @@
+
+ */
+class NeosPageCollection extends \ArrayObject
+{
+ function __construct(
+ NeosPageDTO ...$items
+ ) {
+ parent::__construct($items);
+ }
+}
diff --git a/src/Neos/DTO/NeosPageDTO.php b/src/Neos/DTO/NeosPageDTO.php
new file mode 100644
index 0000000..7b3e8b3
--- /dev/null
+++ b/src/Neos/DTO/NeosPageDTO.php
@@ -0,0 +1,16 @@
+neosClient->get('neos/shopware-api/pagetree', [
+ 'headers' => [
+ 'x-sw-sales-channel-id' => $salesChannelId,
+ 'x-sw-language-id' => $languageId,
+ ]
+ ]);
+ $content = $response->getBody()->getContents();
+
+ return $this->serializer->deserialize($content, NeosPageCollection::class, 'json', [
+ UnwrappingDenormalizer::UNWRAP_PATH => '[pages]'
+ ]);
+ }
+}
diff --git a/src/Resolver/NeosDimensionResolver.php b/src/Resolver/NeosDimensionResolver.php
index dc629c5..0c9d292 100644
--- a/src/Resolver/NeosDimensionResolver.php
+++ b/src/Resolver/NeosDimensionResolver.php
@@ -10,6 +10,9 @@
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Request;
+/**
+ * @deprecated use languageId and salesChannelId
+ */
class NeosDimensionResolver
{
protected array $dimensions = [];
diff --git a/src/Resources/app/administration/src/module/neos/page/neos/index.js b/src/Resources/app/administration/src/module/neos/page/neos/index.js
index f1b413f..660177a 100644
--- a/src/Resources/app/administration/src/module/neos/page/neos/index.js
+++ b/src/Resources/app/administration/src/module/neos/page/neos/index.js
@@ -1,5 +1,7 @@
import template from './neos-index.html.twig';
import './neos-index.scss';
+const { Criteria } = Shopware.Data;
+const { api } = Shopware.Context;
const getNeosBaseUri = async () => {
const configService = Shopware.Service('systemConfigApiService');
@@ -39,6 +41,8 @@ Shopware.Component.register('neos-index', {
data() {
return {
+ isLoading: true,
+ iframeSrc: null,
config: {
neosLoginRoute: null,
token: null,
@@ -49,14 +53,13 @@ Shopware.Component.register('neos-index', {
};
},
- create() {
- this.createdComponent();
+ created() {
+ Shopware.Store.get('adminMenu').collapseSidebar();
},
mounted() {
this.$nextTick(async () => {
await this.loadConfig();
- const form = this.$refs.neosIframeForm;
const neosBaseUri = await getNeosBaseUri();
this.inactiveConfiguration = !neosBaseUri;
if (this.inactiveConfiguration) {
@@ -75,11 +78,9 @@ Shopware.Component.register('neos-index', {
return;
}
- form.action = this.config.neosLoginRoute;
- this.addHiddenInputToForm(form, 'shopwareAccessToken', this.config.token);
- this.addHiddenInputToForm(form, 'apiUrl', this.config.apiUrl);
- this.addHiddenInputToForm(form, 'shopwareVersion', this.config.shopwareVersion);
- form.submit();
+ this.loadNeosIntoIframe().catch((error) => {
+ console.error('Failed to load Neos into iframe:', error);
+ });
// send refreshed token to Neos
loginService.addOnTokenChangedListener(async () => {
@@ -92,7 +93,7 @@ Shopware.Component.register('neos-index', {
}
});
- if(!this.config.neosLoginRoute) {
+ if (!this.config.neosLoginRoute) {
console.error('Could not refresh token: neosLoginRoute is undefined');
return;
}
@@ -101,8 +102,8 @@ Shopware.Component.register('neos-index', {
{
nlxShopwareMessageType: 'token-changed',
token: token,
- apiUrl: Shopware.Context.api.schemeAndHttpHost,
- shopwareVersion: this.config.shopwareVersion
+ apiUrl: api.schemeAndHttpHost,
+ shopwareVersion: this.config.shopwareVersion,
},
this.config.neosLoginRoute
);
@@ -111,8 +112,14 @@ Shopware.Component.register('neos-index', {
},
computed: {
- localeRepository() {
- return this.repositoryFactory.create('locale');
+ salesChannelRepository() {
+ return this.repositoryFactory.create('sales_channel');
+ },
+ async getSalesChannels() {
+ const criteria = new Criteria();
+ criteria.addFilter(Criteria.equals('typeId', '8a243080f92e4c719546314b577cf82b')); // SALES_CHANNEL_TYPE_STOREFRONT
+
+ return await this.salesChannelRepository.search(criteria, api);
}
},
@@ -131,7 +138,7 @@ Shopware.Component.register('neos-index', {
throw new Error('Failed to retrieve Neos token: ' + response.data.message);
}
});
- this.config.apiUrl = Shopware.Context.api.schemeAndHttpHost;
+ this.config.apiUrl = api.schemeAndHttpHost;
const currentRoute = this.$router.currentRoute;
const neosBaseUri = await getNeosBaseUri();
@@ -145,44 +152,37 @@ Shopware.Component.register('neos-index', {
}
},
- createdComponent() {
- Shopware.Store.get('adminMenu').collapseSidebar();
+ async loadNeosIntoIframe() {
+ const salesChannel = await this.getSalesChannels.then(sc => sc.first());
+ const response = await fetch(this.config.neosLoginRoute, {
+ method: 'POST',
+ credentials: 'include',
+ redirect: 'follow',
+ headers: {
+ 'x-sw-language-id': api.language.id,
+ 'x-sw-sales-channel-id': salesChannel.id,
+ 'x-sw-context-token': salesChannel.accessKey,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ shopwareAccessToken: this.config.token,
+ apiUrl: this.config.apiUrl,
+ shopwareVersion: this.config.shopwareVersion,
+ })
+ });
+
+ response.json().then(content => {
+ this.iframeSrc = content.iframeUri;
+ this.isLoading = false;
+ });
},
async getDetailQueryParams() {
const queryParams = [];
-
- const fallbackLocale = Shopware.Context.app.fallbackLocale;
-
- let localeId = Shopware.Context.api.language.localeId;
- let languageParam = "";
-
- const criteria = new Shopware.Data.Criteria();
- criteria.addFilter(Shopware.Data.Criteria.equals('id', localeId));
-
- const locales = await this.localeRepository.search(criteria, Shopware.Context.api);
- const locale = locales.first();
-
- if (!locale) {
- console.warn('No locale found using fallback:', fallbackLocale);
- languageParam = fallbackLocale;
- } else {
- languageParam = locale.code;
- }
-
- queryParams.push({ key: 'nodeIdentifier', value: this.$router.currentRoute.value.params.nodeIdentifier });
- queryParams.push({ key: 'language', value: languageParam });
- queryParams.push({ key: 'swEntityId', value: this.$router.currentRoute.value.params.entityId ?? '' });
- queryParams.push({ key: 'swEntityName', value: this.$router.currentRoute.value.params.entityName ?? '' });
+ queryParams.push({key: 'nodeIdentifier', value: this.$router.currentRoute.value.params.nodeIdentifier});
+ queryParams.push({key: 'swEntityId', value: this.$router.currentRoute.value.params.entityId ?? ''});
+ queryParams.push({key: 'swEntityName', value: this.$router.currentRoute.value.params.entityName ?? ''});
return queryParams;
- },
-
- addHiddenInputToForm(form, name, value) {
- const input = document.createElement('input');
- input.type = 'hidden';
- input.name = name;
- input.value = value;
- form.appendChild(input);
}
}
});
diff --git a/src/Resources/app/administration/src/module/neos/page/neos/neos-index.html.twig b/src/Resources/app/administration/src/module/neos/page/neos/neos-index.html.twig
index a8713e0..8e3ee25 100644
--- a/src/Resources/app/administration/src/module/neos/page/neos/neos-index.html.twig
+++ b/src/Resources/app/administration/src/module/neos/page/neos/neos-index.html.twig
@@ -6,8 +6,8 @@
{# Embeded Neos if the installation is active and has a valid URL #}
-
-
-
+
+
diff --git a/src/Resources/app/administration/src/module/sw-category/component/sw-category-layout-card/index.js b/src/Resources/app/administration/src/module/sw-category/component/sw-category-layout-card/index.js
index f70d653..01de14e 100644
--- a/src/Resources/app/administration/src/module/sw-category/component/sw-category-layout-card/index.js
+++ b/src/Resources/app/administration/src/module/sw-category/component/sw-category-layout-card/index.js
@@ -15,7 +15,7 @@ export default {
return;
}
- return this.cmsPage.extensions?.nlxNeosNode?.nodeIdentifier !== undefined;
+ return this.cmsPage.extensions?.nlxNeosNode?.neosConnection;
},
},
@@ -29,15 +29,11 @@ export default {
// this should never happen
console.error('No cmsPage provided');
}
- if (!this.cmsPage.extensions?.nlxNeosNode?.nodeIdentifier) {
- // this should never happen
- console.error('No nodeIdentifier provided');
- }
this.$router.push({
name: 'nlx.neos.detail',
params: {
- nodeIdentifier: this.cmsPage.extensions.nlxNeosNode.nodeIdentifier,
+ cmsPageId: this.cmsPage.id,
entityId: this.category.id,
entityName: 'category',
},
diff --git a/src/Resources/app/administration/src/module/sw-cms/component/sw-cms-list-item/index.js b/src/Resources/app/administration/src/module/sw-cms/component/sw-cms-list-item/index.js
index d69cdc0..e5c9bce 100644
--- a/src/Resources/app/administration/src/module/sw-cms/component/sw-cms-list-item/index.js
+++ b/src/Resources/app/administration/src/module/sw-cms/component/sw-cms-list-item/index.js
@@ -10,7 +10,7 @@ export default {
return false;
}
- return this.page.extensions?.nlxNeosNode?.nodeIdentifier !== undefined;
+ return this.page.extensions?.nlxNeosNode?.neosConnection;
},
},
}
diff --git a/src/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/index.js b/src/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/index.js
index 2fecf56..1663678 100644
--- a/src/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/index.js
+++ b/src/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/index.js
@@ -35,55 +35,15 @@ export default {
}
},
methods: {
- onChangeNeosNodeIdentifier(page) {
- this.neosNodeIdentifier = page.neosNodeIdentifier;
- this.showNodeIdentifierChangeModal = true;
- this.cmsPage = page;
- },
-
- onCloseNodeIdentifierChangeModal() {
- this.showNodeIdentifierChangeModal = false;
- this.currentPage = null;
- this.$emit('closeNeosNodeIdentifierModal');
- },
-
- async onConfirmNodeIdentifierChangeModal() {
- if (!this.currentPage) {
- this.createNotificationError({
- //TODO translate
- title: 'Error',
- message: 'Failed could not resolve cms_page: ' + 1742907120
- });
- return;
- }
-
- try {
- await this.pageRepository.save(this.currentPage, Shopware.Context.api);
- this.$emit('closeNeosNodeIdentifierModal');
- this.createNotificationSuccess({
- //TODO translate
- title: 'Success',
- message: 'Custom data saved successfully!'
- });
- this.showNodeIdentifierChangeModal = false;
- } catch (error) {
- this.createNotificationError({
- //TODO translate
- title: 'Error',
- message: 'Failed to save data.'
- });
- }
- },
-
isNeosPage(page) {
- return page.extensions?.nlxNeosNode?.nodeIdentifier !== undefined;
+ return page.extensions?.nlxNeosNode?.neosConnection;
},
openInNeos(page) {
this.$router.push({
name: 'nlx.neos.detail',
params: {
- nodeIdentifier: page.extensions.nlxNeosNode.nodeIdentifier
+ cmsPageId: page.id
},
});
},
diff --git a/src/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/sw-cms-list.html.twig b/src/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/sw-cms-list.html.twig
index 7c25378..e69de29 100644
--- a/src/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/sw-cms-list.html.twig
+++ b/src/Resources/app/administration/src/module/sw-cms/page/sw-cms-list/sw-cms-list.html.twig
@@ -1,48 +0,0 @@
-{% block sw_cms_list_listing_list_item_option_duplicate %}
- {% parent %}
-
- {{ $tc('sw-cms.components.cmsListItem.neos.changeIdentifier') }}
-
-{% endblock %}
-
-{% block sw_cms_list_rename_modal %}
- {% parent %}
-
-
-
- {{ $tc('sw-cms.components.cmsListItem.neos.changeIdentifier') }}
-
-
-
-
-
- {{ $tc('global.default.cancel') }}
-
-
-
- {{ $tc('sw-cms.components.cmsListItem.neos.button.changeIdentifier') }}
-
-
-
-{% endblock %}
diff --git a/src/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/index.js b/src/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/index.js
index 8db1b00..d78f372 100644
--- a/src/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/index.js
+++ b/src/Resources/app/administration/src/module/sw-product/component/sw-product-layout-assignment/index.js
@@ -9,7 +9,7 @@ export default {
return;
}
- return this.cmsPage.extensions?.nlxNeosNode?.nodeIdentifier !== undefined;
+ return this.cmsPage.extensions?.nlxNeosNode?.neosConnection;
},
},
@@ -23,15 +23,11 @@ export default {
// this should never happen
console.error('No cmsPage provided');
}
- if (!this.cmsPage.extensions?.nlxNeosNode?.nodeIdentifier) {
- // this should never happen
- console.error('No nodeIdentifier provided');
- }
this.$router.push({
name: 'nlx.neos.detail',
params: {
- nodeIdentifier: this.cmsPage.extensions.nlxNeosNode.nodeIdentifier,
+ cmsPageId: this.cmsPage.id,
entityId: this.$router.currentRoute.value.params.id,
entityName: 'product',
},
diff --git a/src/Resources/app/administration/src/snippet/de_DE.json b/src/Resources/app/administration/src/snippet/de_DE.json
index 884f335..69eb1cd 100644
--- a/src/Resources/app/administration/src/snippet/de_DE.json
+++ b/src/Resources/app/administration/src/snippet/de_DE.json
@@ -6,19 +6,6 @@
}
}
},
- "sw-cms": {
- "components": {
- "cmsListItem": {
- "neos": {
- "COMMENT": "FIXME - this button has to be removed in v1",
- "changeIdentifier": "Neos Node identifier ändern",
- "button": {
- "changeIdentifier": "Bestätigen"
- }
- }
- }
- }
- },
"sw-category": {
"base": {
"cms": {
diff --git a/src/Resources/app/administration/src/snippet/en_GB.json b/src/Resources/app/administration/src/snippet/en_GB.json
index 4dbad43..46da71c 100644
--- a/src/Resources/app/administration/src/snippet/en_GB.json
+++ b/src/Resources/app/administration/src/snippet/en_GB.json
@@ -6,19 +6,6 @@
}
}
},
- "sw-cms": {
- "components": {
- "cmsListItem": {
- "neos": {
- "COMMENT": "FIXME - this button has to be removed in v1",
- "changeIdentifier": "Change Neos Node identifier",
- "button": {
- "changeIdentifier": "Confirm"
- }
- }
- }
- }
- },
"sw-category": {
"base": {
"cms": {
diff --git a/src/Resources/views/storefront/page/neosPage.html.twig b/src/Resources/views/storefront/page/neosPage.html.twig
new file mode 100644
index 0000000..219c5e8
--- /dev/null
+++ b/src/Resources/views/storefront/page/neosPage.html.twig
@@ -0,0 +1,19 @@
+{% sw_extends '@Storefront/storefront/base.html.twig' %}
+
+{# @var page \Shopware\Storefront\Page\LandingPage\LandingPage|\Shopware\Storefront\Page\CategoryPage\CategoryPage #}
+{% block base_main_inner %}
+
+ {% block page_content %}
+
+ {% block cms_content %}
+ {% set cmsPageClasses = ('cms-page ' ~ cmsPage.cssClass|striptags)|trim %}
+ {% set landingPage = {} %}
+
+ {% block page_content_blocks %}
+ {% sw_include '@Storefront/storefront/page/content/detail.html.twig' with {cmsPage: cmsPage, landingPage: landingPage} %}
+ {% endblock %}
+
+ {% endblock %}
+ {% endblock %}
+
+{% endblock %}
diff --git a/src/Routing/Router.php b/src/Routing/Router.php
new file mode 100644
index 0000000..765b901
--- /dev/null
+++ b/src/Routing/Router.php
@@ -0,0 +1,72 @@
+inner->setContext($context);
+ }
+
+ public function getContext(): RequestContext
+ {
+ return $this->inner->getContext();
+ }
+
+ public function getRouteCollection(): RouteCollection
+ {
+ return $this->inner->getRouteCollection();
+ }
+
+ public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
+ {
+ return $this->inner->generate($name, $parameters, $referenceType);
+ }
+
+ public function match(string $pathinfo): array
+ {
+ try {
+ return $this->inner->match($pathinfo);
+ } catch (\Exception $e) {
+ try {
+ return $this->matchNeosPath($pathinfo);
+ } catch (\Exception) {
+ }
+
+ throw $e;
+ }
+ }
+
+ protected function matchNeosPath(string $pathinfo): array
+ {
+
+ return [
+ 'neos' => 1,
+ '_routeScope' => ['storefront'],
+ '_controller'=> NeosPageController::class.'::index',
+ ];
+ }
+
+ public function warmUp(string $cacheDir, ?string $buildDir = null): array
+ {
+ return $this->inner->warmUp($cacheDir, $buildDir);
+ }
+}
diff --git a/src/Serializer/CmsBlock/NeosCmsBlockCollectionDenormalizer.php b/src/Serializer/CmsBlock/NeosCmsBlockCollectionDenormalizer.php
new file mode 100644
index 0000000..91534dd
--- /dev/null
+++ b/src/Serializer/CmsBlock/NeosCmsBlockCollectionDenormalizer.php
@@ -0,0 +1,51 @@
+serializer = $serializer;
+ }
+
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): NeosCmsBlockCollection
+ {
+ $cmsBlockCollection = new NeosCmsBlockCollection();
+ $position = 0;
+ foreach ($data as $blockData) {
+ $cmsBlock = $this->serializer->denormalize($blockData, NeosCmsBlockEntity::class, $format, $context);
+ $cmsBlock->setPosition($position);
+ $cmsBlockCollection->add($cmsBlock);
+ $position++;
+ }
+
+ return $cmsBlockCollection;
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosCmsBlockCollection::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosCmsBlockCollection::class => false,
+ ];
+ }
+}
diff --git a/src/Serializer/CmsBlock/NeosCmsBlockEntityDenormalizer.php b/src/Serializer/CmsBlock/NeosCmsBlockEntityDenormalizer.php
new file mode 100644
index 0000000..b0c4119
--- /dev/null
+++ b/src/Serializer/CmsBlock/NeosCmsBlockEntityDenormalizer.php
@@ -0,0 +1,84 @@
+serializer = $serializer;
+ }
+
+ public function denormalize(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): NeosCmsBlockEntity {
+ if (!isset($data['type'])) {
+ throw new \RuntimeException('Block type is not set');
+ }
+
+ $cmsBlock = new NeosCmsBlockEntity();
+ $fieldVisibility = new FieldVisibility([]);
+ $blockId = $data['id'] ?? Uuid::randomHex();
+
+ $cmsBlock->setType($data['type']);
+ $cmsBlock->setVersionId(DEFAULTS::LIVE_VERSION);
+ $cmsBlock->setSectionId($context['sectionId'] ?? '');
+ $cmsBlock->setSectionPosition($data['sectionPosition'] ?? 'main');
+ $cmsBlock->internalSetEntityData('cms_block', $fieldVisibility);
+ $cmsBlock->setId($blockId);
+ if (array_key_exists('hiddenEditorRendering', $data)) {
+ $cmsBlock->setCustomFields(['hiddenEditorRendering' => $data['hiddenEditorRendering']]);
+ }
+ $cmsBlock->setCssClass($data['cssClass'] ?? '');
+
+ $slots = $this->serializer->denormalize(
+ $data['slots'],
+ NeosCmsSlotCollection::class,
+ $format,
+ ['blockId' => $blockId]
+ );
+ $cmsBlock->setSlots($slots);
+ $cmsBlock->setCreatedAt(new \DateTime());
+ $cmsBlock->setVisibility([
+ 'mobile' => true,
+ 'desktop' => true,
+ 'tablet' => true
+ ]);
+ $cmsBlock->setLocked(true);
+ $cmsBlock->setCmsSectionVersionId(DEFAULTS::LIVE_VERSION);
+
+ return $cmsBlock;
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosCmsBlockEntity::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosCmsBlockEntity::class => false,
+ ];
+ }
+}
diff --git a/src/Serializer/CmsSection/NeosCmsSectionCollectionDenormalizer.php b/src/Serializer/CmsSection/NeosCmsSectionCollectionDenormalizer.php
new file mode 100644
index 0000000..068242f
--- /dev/null
+++ b/src/Serializer/CmsSection/NeosCmsSectionCollectionDenormalizer.php
@@ -0,0 +1,52 @@
+serializer->denormalize($sectionData, NeosCmsSectionEntity::class, $format, $context);
+ $cmsSection->setPosition($position);
+ $cmsSectionCollection->add($cmsSection);
+ $position++;
+ }
+
+ return $cmsSectionCollection;
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosCmsSectionCollection::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosCmsSectionCollection::class => false,
+ ];
+ }
+
+ public function setSerializer(SerializerInterface $serializer): void
+ {
+ $this->serializer = $serializer;
+ }
+}
diff --git a/src/Serializer/CmsSection/NeosCmsSectionEntityDenormalizer.php b/src/Serializer/CmsSection/NeosCmsSectionEntityDenormalizer.php
new file mode 100644
index 0000000..2d12377
--- /dev/null
+++ b/src/Serializer/CmsSection/NeosCmsSectionEntityDenormalizer.php
@@ -0,0 +1,67 @@
+serializer = $serializer;
+ }
+
+ public function denormalize(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): NeosCmsSectionEntity {
+ $cmsSection = new NeosCmsSectionEntity();
+ $fieldVisibility = new FieldVisibility([]);
+ $cmsSection->setId(Uuid::randomHex());
+ $cmsSection->setType($data['type']);
+ $cmsSection->setVersionId(Defaults::LIVE_VERSION);
+ $cmsSection->setSizingMode($data['sizingMode'] ?? 'boxed');
+ $cmsSection->setMobileBehavior($data['mobileBehavior'] ?? 'wrap');
+ $cmsSection->setBackgroundColor($data['backgroundColor'] ?? '');
+ $cmsSection->setBackgroundMediaMode($data['backgroundMediaMode'] ?? 'cover');
+ $cmsSection->internalSetEntityData('cms_section', $fieldVisibility);
+ $blocks = $this->serializer->denormalize(
+ $data['blocks'],
+ NeosCmsBlockCollection::class,
+ $format,
+ ['sectionId' => $cmsSection->getId()]
+ );
+ $cmsSection->setBlocks($blocks);
+
+ return $cmsSection;
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosCmsSectionEntity::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosCmsSectionEntity::class => false,
+ ];
+ }
+}
diff --git a/src/Serializer/CmsSlot/NeosCmsSlotCollectionDenormalizer.php b/src/Serializer/CmsSlot/NeosCmsSlotCollectionDenormalizer.php
new file mode 100644
index 0000000..190a141
--- /dev/null
+++ b/src/Serializer/CmsSlot/NeosCmsSlotCollectionDenormalizer.php
@@ -0,0 +1,55 @@
+serializer = $serializer;
+ }
+
+ public function denormalize(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): NeosCmsSlotCollection {
+ $cmsSlotCollection = new NeosCmsSlotCollection();
+ foreach ($data as $slotData) {
+ if (!isset($slotData['type'])) {
+ throw new \RuntimeException('Slot type is not set');
+ }
+ $slotEntity = $this->serializer->denormalize($slotData, NeosCmsSlotEntity::class, $format);
+ $cmsSlotCollection->add($slotEntity);
+ }
+
+ return $cmsSlotCollection;
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosCmsSlotCollection::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosCmsSlotCollection::class => false,
+ ];
+ }
+}
diff --git a/src/Serializer/CmsSlot/NeosCmsSlotEntityDenormalizer.php b/src/Serializer/CmsSlot/NeosCmsSlotEntityDenormalizer.php
new file mode 100644
index 0000000..46d7908
--- /dev/null
+++ b/src/Serializer/CmsSlot/NeosCmsSlotEntityDenormalizer.php
@@ -0,0 +1,64 @@
+serializer = $serializer;
+ }
+
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): NeosCmsSlotEntity
+ {
+ $cmsSlotEntity = new NeosCmsSlotEntity();
+ $fieldVisibility = new FieldVisibility([]);
+ $data['_class'] = CmsSlotEntity::class;
+ assert($cmsSlotEntity instanceof CmsSlotEntity);
+ $cmsSlotEntity->setType($data['type']);
+ $cmsSlotEntity->setVersionId(Defaults::LIVE_VERSION);
+ $cmsSlotEntity->setId($data['id'] ?? Uuid::randomHex());
+ $cmsSlotEntity->setSlot($data['slot'] ?? 'content');
+ $cmsSlotEntity->setConfig($data['config'] ?? []);
+ $cmsSlotEntity->setTranslated(["config" => $data['config'] ?? []]);
+ $cmsSlotEntity->setBlockId($context['blockId'] ?? '');
+ $cmsSlotEntity->internalSetEntityData('cms_slot', $fieldVisibility);
+
+ if (count($data['fieldConfig']) > 0) {
+ $fieldConfigs = $this->serializer->denormalize($data['fieldConfig'], NeosFieldConfigCollection::class, $format);
+ $cmsSlotEntity->setFieldConfig($fieldConfigs);
+ }
+
+ return $cmsSlotEntity;
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosCmsSlotEntity::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosCmsSlotEntity::class => false,
+ ];
+ }
+}
diff --git a/src/Serializer/FieldConfig/NeosFieldConfigCollectionDenormalizer.php b/src/Serializer/FieldConfig/NeosFieldConfigCollectionDenormalizer.php
new file mode 100644
index 0000000..cb95c35
--- /dev/null
+++ b/src/Serializer/FieldConfig/NeosFieldConfigCollectionDenormalizer.php
@@ -0,0 +1,52 @@
+serializer = $serializer;
+ }
+
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): NeosFieldConfigCollection
+ {
+ $fieldConfigCollection = new NeosFieldConfigCollection();
+ foreach ($data as $fieldConfigData) {
+ $fieldConfig = $this->serializer->deserialize(
+ json_encode($fieldConfigData),
+ NeosFieldConfig::class,
+ 'json'
+ );
+ $fieldConfigCollection->add($fieldConfig);
+ }
+
+ return $fieldConfigCollection;
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosFieldConfigCollection::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosFieldConfigCollection::class => false,
+ ];
+ }
+}
diff --git a/src/Serializer/FieldConfig/NeosFieldConfigDenormalizer.php b/src/Serializer/FieldConfig/NeosFieldConfigDenormalizer.php
new file mode 100644
index 0000000..e15bcf7
--- /dev/null
+++ b/src/Serializer/FieldConfig/NeosFieldConfigDenormalizer.php
@@ -0,0 +1,45 @@
+serializer = $serializer;
+ }
+
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): NeosFieldConfig
+ {
+ return new NeosFieldConfig(
+ $data['name'],
+ $data['source'],
+ $data['value']
+ );
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosFieldConfig::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosFieldConfig::class => false,
+ ];
+ }
+}
diff --git a/src/Serializer/Page/NeosPageCollectionDenormalizer.php b/src/Serializer/Page/NeosPageCollectionDenormalizer.php
new file mode 100644
index 0000000..a7cd8c3
--- /dev/null
+++ b/src/Serializer/Page/NeosPageCollectionDenormalizer.php
@@ -0,0 +1,44 @@
+serializer = $serializer;
+ }
+
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
+ {
+ $pages = $this->serializer->denormalize($data, NeosPageDTO::class.'[]', $format, $context);
+ return new $type(...$pages);
+ }
+
+ public function supportsDenormalization(
+ mixed $data,
+ string $type,
+ ?string $format = null,
+ array $context = []
+ ): bool {
+ return $type === NeosPageCollection::class;
+ }
+
+ public function getSupportedTypes(?string $format): array
+ {
+ return [
+ NeosPageCollection::class => true
+ ];
+ }
+}
diff --git a/src/Service/ContentExchangeService.php b/src/Service/ContentExchangeService.php
index 53829d2..a4a601a 100644
--- a/src/Service/ContentExchangeService.php
+++ b/src/Service/ContentExchangeService.php
@@ -4,25 +4,16 @@
namespace nlxNeosContent\Service;
-use DateTime;
-use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
+use nlxNeosContent\Core\Content\Cms\Aggregate\CmsSection\NeosCmsSectionCollection;
use nlxNeosContent\Error\NeosContentFetchException;
use Psr\Http\Client\ClientInterface;
use Shopware\Core\Content\Cms\Aggregate\CmsBlock\CmsBlockCollection;
-use Shopware\Core\Content\Cms\Aggregate\CmsBlock\CmsBlockEntity;
use Shopware\Core\Content\Cms\Aggregate\CmsSection\CmsSectionCollection;
-use Shopware\Core\Content\Cms\Aggregate\CmsSection\CmsSectionEntity;
-use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotCollection;
-use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
+use Shopware\Core\Content\Cms\CmsPageEntity;
use Shopware\Core\Content\Cms\DataResolver\CmsSlotsDataResolver;
-use Shopware\Core\Content\Cms\DataResolver\FieldConfig;
-use Shopware\Core\Content\Cms\DataResolver\FieldConfigCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
-use Shopware\Core\Defaults;
-use Shopware\Core\Framework\DataAbstractionLayer\FieldVisibility;
-use Shopware\Core\Framework\Struct\Struct;
-use Shopware\Core\Framework\Uuid\Uuid;
+use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Serializer\SerializerInterface;
@@ -37,169 +28,58 @@ public function __construct(
}
/**
- * @throws GuzzleException
+ * @throws NeosContentFetchException
*/
public function getAlternativeCmsSectionsFromNeos(
- string $nodeIdentifier,
- Struct $dimensions,
+ CmsPageEntity $cmsPage,
+ string $languageId,
+ string $salesChannelId
): CmsSectionCollection {
- $elements = $this->fetchNeosContentByNodeIdentifierAndDimension(
- $nodeIdentifier,
- $dimensions
+ $elements = $this->fetchNeosContentForCmsPage(
+ $cmsPage,
+ $languageId,
+ $salesChannelId
);
- $sections = $this->serializer->decode($elements, 'json');
- $cmsSectionCollection = new CmsSectionCollection();
- $position = 0;
-
- foreach ($sections as $sectionData) {
- $cmsSection = $this->createCmsSectionFromSectionData($sectionData, $position, Uuid::randomHex());
- if ($cmsSection === null) {
- continue;
- }
-
- $cmsSectionCollection->add($cmsSection);
- $position++;
- }
-
- return $cmsSectionCollection;
- }
-
- public function createCmsSectionFromSectionData(array $sectionData, int $position, string $sectionId): ?CmsSectionEntity
- {
- if (!isset($sectionData['type'])) {
- throw new \RuntimeException('Section type is not set');
- }
-
- $cmsSection = new CmsSectionEntity();
- $fieldVisibility = new FieldVisibility([
-
- ]);
- $cmsSection->setType($sectionData['type']);
- $cmsSection->setPosition($position);
- $cmsSection->setVersionId(DEFAULTS::LIVE_VERSION);
- $cmsSection->setSizingMode($sectionData['sizingMode'] ?? 'boxed');
- $cmsSection->setMobileBehavior($sectionData['mobileBehavior'] ?? 'wrap');
- $cmsSection->setBackgroundColor($sectionData['backgroundColor'] ?? '');
- $cmsSection->setBackgroundMediaMode($sectionData['backgroundMediaMode'] ?? 'cover');
- $cmsSection->setId($sectionId);
- $cmsSection->internalSetEntityData('cms_section', $fieldVisibility);
- $cmsSection->setBlocks($this->blockCollectionFromArray($sectionData['blocks'] ?? [], $sectionId));
-
- return $cmsSection;
+ return $this->serializer->denormalize($elements, NeosCmsSectionCollection::class,'json');
}
- public function blockCollectionFromArray(array $blocks, string $cmsSectionId): CmsBlockCollection
+ public function fetchCmsSectionsFromNeosByPath(string $pathInfo, SalesChannelContext $salesChannelContext): CmsSectionCollection
{
- $cmsBlockCollection = new CmsBlockCollection();
- $blockPosition = 0;
- foreach ($blocks as $blockData) {
- $cmsBlock = $this->createCmsBlockFromBlockData($blockData, $blockPosition, $cmsSectionId);
- if ($cmsBlock === null) {
- continue;
- }
-
- $cmsBlock->setCreatedAt(new DateTime());
- $cmsBlock->setVisibility([
- 'mobile' => true,
- 'desktop' => true,
- 'tablet' => true
- ]);
- $cmsBlock->setLocked(true);
- $cmsBlock->setCmsSectionVersionId(DEFAULTS::LIVE_VERSION);
-
- $cmsBlockCollection->add($cmsBlock);
- $blockPosition++;
- }
-
- return $cmsBlockCollection;
- }
-
- public function createCmsBlockFromBlockData(array $blockData, int $position, string $sectionId): ?CmsBlockEntity
- {
- if (!isset($blockData['type'])) {
- throw new \RuntimeException('Block type is not set');
- }
-
- $cmsBlock = new CmsBlockEntity();
- $fieldVisibility = new FieldVisibility([
-
+ $response = $this->neosClient->get(sprintf("/neos/shopware-api/content-by-path/%s", trim($pathInfo, '/')), [
+ 'headers' => [
+ 'x-sw-language-id' => $salesChannelContext->getLanguageId(),
+ 'x-sw-sales-channel-id' => $salesChannelContext->getSalesChannelId(),
+ ]
]);
- $blockId = $blockData['id'] ?? Uuid::randomHex();
-
- $cmsBlock->setType($blockData['type']);
- $cmsBlock->setPosition($position);
- $cmsBlock->setVersionId(DEFAULTS::LIVE_VERSION);
- $cmsBlock->setSectionId($sectionId);
- $cmsBlock->setSectionPosition($blockData['sectionPosition'] ?? 'main');
- $cmsBlock->internalSetEntityData('cms_block', $fieldVisibility);
- $cmsBlock->setId($blockId);
- if (array_key_exists('hiddenEditorRendering', $blockData)) {
- $cmsBlock->setCustomFields(['hiddenEditorRendering' => $blockData['hiddenEditorRendering']]);
- }
- $cmsBlock->setCssClass($blockData['cssClass'] ?? '');
- $slots = $this->slotCollectionFromArray($blockData['slots'] ?? [], $blockId);
- $cmsBlock->setSlots($slots);
-
- return $cmsBlock;
- }
-
- private function slotCollectionFromArray(array $slots, string $blockId): CmsSlotCollection
- {
- $cmsSlotCollection = new CmsSlotCollection();
- foreach ($slots as $slot) {
- if (!isset($slot['type'])) {
- throw new \RuntimeException('Slot type is not set');
- }
-
- $fieldVisibility = new FieldVisibility([]);
- $cmsSlot = $this->deserializeEntityFromJson($slot['data'], CmsSlotEntity::class);
- $cmsSlot->setType($slot['type']);
- $cmsSlot->setVersionId(Defaults::LIVE_VERSION);
- $cmsSlot->setId($slot['id'] ?? Uuid::randomHex());
- $cmsSlot->setSlot($slot['slot'] ?? 'content');
- $cmsSlot->setConfig($slot['config'] ?? []);
- $cmsSlot->setTranslated($this->getTranslatedConfigFromConfig($slot['config'] ?? []));
- $cmsSlot->setBlockId($blockId);
- $cmsSlot->internalSetEntityData('cms_slot', $fieldVisibility);
-
- if (count($slot['fieldConfig']) > 0) {
- $cmsSlot->setFieldConfig($this->fieldConfigCollectionFromArray($slot['fieldConfig'] ?? []));
- }
-
- $cmsSlotCollection->add($cmsSlot);
- }
-
- return $cmsSlotCollection;
- }
-
- private function fieldConfigCollectionFromArray(array $data): FieldConfigCollection
- {
- $fieldConfigCollection = new FieldConfigCollection();
- foreach ($data as $fieldConfigData) {
- $fieldConfig = new FieldConfig(
- $fieldConfigData['name'],
- $fieldConfigData['source'],
- $fieldConfigData['value']
- );
- $fieldConfigCollection->add($fieldConfig);
- }
-
- return $fieldConfigCollection;
+ return $this->serializer->denormalize($response->getBody()->getContents(), NeosCmsSectionCollection::class,'json');
}
/**
* @throws NeosContentFetchException
*/
- private function fetchNeosContentByNodeIdentifierAndDimension(string $nodeIdentifier, Struct $dimensions): string
- {
- $dimensionString = $this->resolveDimensionString($dimensions);
+ private function fetchNeosContentForCmsPage(
+ CmsPageEntity $cmsPage,
+ string $languageId,
+ string $salesChannelId
+ ): string {
try {
- $response = $this->neosClient->get(sprintf("/neos/shopware-api/content/%s__%s/", $nodeIdentifier, $dimensionString));
+ $response = $this->neosClient->get(sprintf("/neos/shopware-api/content/%s/", $cmsPage->getId()), [
+ 'headers' => [
+ 'x-sw-language-id' => $languageId,
+ 'x-sw-sales-channel-id' => $salesChannelId,
+ ]
+ ]);
} catch (RequestException $e) {
throw new NeosContentFetchException (
- sprintf('Failed to fetch content from Neos for node identifier "%s" and dimensions "%s": %s', $nodeIdentifier, $dimensionString, $e->getMessage()),
+ sprintf(
+ 'Failed to fetch content from Neos for node CmsPage "%s" for sales channel "%s" and language "%s" with Errormessage: %s',
+ $cmsPage->getName(),
+ $salesChannelId,
+ $languageId,
+ $e->getMessage()
+ ),
1752652016,
$e
);
@@ -209,33 +89,12 @@ private function fetchNeosContentByNodeIdentifierAndDimension(string $nodeIdenti
}
/**
- * @param class-string $classString
- *
- * @template T of Struct
- * @return Struct|T
+ * Loads the slot data into the given blocks for given resolver context.
*/
- private function deserializeEntityFromJson(array $data, string $classString): Struct
- {
- $data['_class'] = $classString;
- return $this->serializer->denormalize($data, $classString, 'json');
- }
-
public function loadSlotData(CmsBlockCollection $blocks, ResolverContext $resolverContext): void
{
$slots = $this->cmsSlotsDataResolver->resolve($blocks->getSlots(), $resolverContext);
$blocks->setSlots($slots);
}
-
- private function getTranslatedConfigFromConfig(array $config): array
- {
- return [
- "config" => $config,
- ];
- }
-
- private function resolveDimensionString(Struct $dimensions): string
- {
- return implode('__', $dimensions->getVars());
- }
}
diff --git a/src/Service/Loader/NeosDimensionLoader.php b/src/Service/Loader/NeosDimensionLoader.php
index 4f2580a..f9f6986 100644
--- a/src/Service/Loader/NeosDimensionLoader.php
+++ b/src/Service/Loader/NeosDimensionLoader.php
@@ -6,6 +6,9 @@
use GuzzleHttp\Exception\GuzzleException;
use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
+/**
+ * @deprecated
+ */
class NeosDimensionLoader extends AbstractNeosDimensionLoader
{
public function __construct(
diff --git a/src/Service/NeosAuthorizationRoleService.php b/src/Service/NeosAuthorizationRoleService.php
index cbabd20..12555d2 100644
--- a/src/Service/NeosAuthorizationRoleService.php
+++ b/src/Service/NeosAuthorizationRoleService.php
@@ -27,38 +27,41 @@ class NeosAuthorizationRoleService
*/
public const
BASIC_PRIVILEGES = [
- 'language:read',
- 'locale:read',
"log_entry:create",
"message_queue_stats:read",
- 'system_config:read',
'acl_role:read',
- 'user:read',
- 'product:read',
'category:read',
+ 'cms_page:read',
+ 'language:read',
+ 'locale:read',
+ 'product:read',
'product_manufacturer:read',
- 'product_sorting:read',
'product_media:read',
- 'property_group_option:read',
+ 'product_sorting:read',
'property_group:read',
+ 'property_group_option:read',
'sales_channel:read',
'sales_channel_domain:read',
'sales_channel_type:read',
+ 'system_config:read',
+ 'user:read',
],
NEOS_VIEWER_PRIVILEGES = [
+ 'cms_page:read',
'neos.viewer',
- 'nlx_neos_node:read',
'neos:read',
- 'cms_page:read',
+ 'nlx_neos_node:read',
],
NEOS_EDITOR_PRIVILEGES = [
- 'neos.viewer',
- 'nlx_neos_node:read',
- 'neos:read',
+ 'cms_page:create',
+ 'cms_page:update',
'neos.editor',
+ 'neos.viewer',
'neos:edit',
+ 'neos:read',
+ 'nlx_neos_node:create',
+ 'nlx_neos_node:read',
'nlx_neos_node:update',
- 'cms_page:update',
];
public function __construct(
diff --git a/src/Service/NeosLayoutPageService.php b/src/Service/NeosLayoutPageService.php
index 9dc7c76..08f475d 100644
--- a/src/Service/NeosLayoutPageService.php
+++ b/src/Service/NeosLayoutPageService.php
@@ -5,7 +5,6 @@
namespace nlxNeosContent\Service;
use Exception;
-use GuzzleHttp\Exception\GuzzleException;
use nlxNeosContent\Core\Content\NeosNode\NeosNodeEntity;
use nlxNeosContent\Core\Notification\NotificationServiceInterface;
use nlxNeosContent\Error\CanNotDeleteDefaultLayoutPageException;
@@ -16,8 +15,6 @@
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
-use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
-use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
@@ -38,121 +35,42 @@ public function __construct(
) {
}
- public function getAvailableFilterPageTypes(): array
- {
- return explode('|', self::AVAILABLE_FILTER_PAGE_TYPES);
- }
-
- public function initialNodeImport(Context $context): void
+ public function getNeosCmsPageTemplates(): array
{
+ $context = Context::createDefaultContext();
try {
- $neosNodes = $this->getNeosLayoutPages($this->getAvailableFilterPageTypes());
+ $response = $this->neosClient->get('/neos/shopware-api/layout/pages', [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ 'x-sw-language-id' => $context->getLanguageId(),
+ ],
+ ]);
} catch (Exception $e) {
- $this->createNotification(
- $context
+ throw new \RuntimeException(
+ sprintf('Failed to fetch Neos layout pages: %s', $e->getMessage()),
+ 1743497435,
+ $e
);
- return;
- }
- $this->createMissingNeosCmsPages($neosNodes, $context);
- }
-
- public function createMissingNeosCmsPages(array $neosNodes, Context $context): void
- {
- $presentNeosNodeIdentifiers = $this->getCmsPageIdWithConnectedNodeIdentifier($context);
- $neosNodesWithoutExistingShopwareLayouts = $this->removeAlreadyPresentNeosLayouts(
- $neosNodes,
- $presentNeosNodeIdentifiers
- );
-
- $this->createCmsPagesForNodes($neosNodesWithoutExistingShopwareLayouts, $context);
- }
-
- /**
- * Fetches Neos layout pages based on the provided page types.
- * @throws GuzzleException
- */
- public function getNeosLayoutPages(array $pageTypes): array
- {
- $language = '/en-GB';
- $contents = [];
- foreach ($pageTypes as $pageType) {
- if (!in_array($pageType, ['product_list', 'product_detail', 'landingpage', 'page'])) {
- throw new \InvalidArgumentException(sprintf('Invalid page type: %s', $pageType), 1743497434);
- }
-
- $apiUrl = sprintf('/neos/shopware-api/layout/pages/%s%s', $pageType, $language);
- try {
- $response = $this->neosClient->get($apiUrl, [
- 'headers' => [
- 'Accept' => 'application/json',
- 'Content-Type' => 'application/json',
- ],
- ]);
- } catch (Exception $e) {
- throw new \RuntimeException(
- sprintf('Failed to fetch Neos layout pages: %s', $e->getMessage()),
- 1743497435,
- $e
- );
- }
-
- $responseData = json_decode($response->getBody()->getContents(), true);
- foreach ($responseData as $value) {
- $contents[] = $value;
- }
- }
-
- return $contents;
- }
-
- private function createCmsPagesForNodes(array $neosNodes, Context $context): void
- {
- $pagesData = [];
- foreach ($neosNodes as $node) {
- $cmsPage = [
- 'id' => md5($node['nodeIdentifier']),
- 'name' => $node['name'],
- 'type' => $node['type'],
- 'versionId' => DEFAULTS::LIVE_VERSION,
- 'nlxNeosNode' => [
- 'versionId' => DEFAULTS::LIVE_VERSION,
- 'cmsPageVersionId' => DEFAULTS::LIVE_VERSION,
- 'nodeIdentifier' => $node['nodeIdentifier'],
- ],
- 'sections' => [
- [
- 'versionId' => DEFAULTS::LIVE_VERSION,
- 'position' => 0,
- 'type' => 'default',
- 'sizingMode' => 'boxed',
- 'blocks' => [],
- ],
- ],
- 'locked' => true,
- ];
-
- $pagesData[] = $cmsPage;
}
- $this->cmsPageRepository->upsert($pagesData, $context);
+ return json_decode($response->getBody()->getContents(), true) ?? [];
}
public function updateNeosCmsPages(array $neosNodes, Context $context): void
{
- $pagesWithConnectedNeosNode = $this->getCmsPageIdWithConnectedNodeIdentifier($context);
-
$pagesData = [];
foreach ($neosNodes as $node) {
- $cmsPageId = array_search($node['nodeIdentifier'], $pagesWithConnectedNeosNode, true);
-
$cmsPageData = [
- 'id' => $cmsPageId,
- 'name' => $node['name'],
+ 'id' => $node['cmsPageId'],
+ 'name' => $node['title'],
'type' => $node['type'],
'nlxNeosNode' => [
+ 'id' => md5($node['cmsPageId'] . '_neos_node'),
'versionId' => DEFAULTS::LIVE_VERSION,
+ 'cmsPageId' => $node['cmsPageId'],
'cmsPageVersionId' => DEFAULTS::LIVE_VERSION,
- 'nodeIdentifier' => $node['nodeIdentifier'],
+ 'neosConnection' => true,
],
];
@@ -169,28 +87,13 @@ public function getNeosNodeEntitiesWithConnectedCmsPage(Context $context): Entit
{
return $this->nlxNeosNodeRepository->search(
(new Criteria())->addFilter(
- new NotFilter(
- MultiFilter::CONNECTION_AND,
- [
- new EqualsFilter('cmsPageId', null),
- ]
- )
+ new EqualsFilter('neosConnection', true)
)->addAssociation('cmsPage'),
$context
)->getEntities();
}
- /**
- * Removes CmsPages that have a node identifier which does not exist in Neos.
- * If a CmsPage is set as default, it will not be removed and a notification will be created.
- *
- * @param array $neosNodes
- * @param Context $context
- * @return void
- *
- * @throws CanNotDeleteDefaultLayoutPageException
- */
- public function removeCmsPagesWithInvalidNodeIdentifiers(array $neosNodes, Context $context): void
+ public function removeObsoleteCmsPages(array $neosNodes, Context $context): void
{
$neosNodeEntities = $this->getNeosNodeEntitiesWithConnectedCmsPage($context);
$configs = $this->systemConfigService->get('core.cms');
@@ -204,14 +107,14 @@ public function removeCmsPagesWithInvalidNodeIdentifiers(array $neosNodes, Conte
/** @var NeosNodeEntity $neosNodeEntity */
foreach ($neosNodeEntities as $neosNodeEntity) {
- if (!$neosNodeEntity->getNodeIdentifier()) {
+ if (!$neosNodeEntity->getNeosConnection()) {
continue;
}
- $nodeIdentifier = array_column($neosNodes, 'nodeIdentifier');
- if (in_array($neosNodeEntity->getNodeIdentifier(), $nodeIdentifier)) {
+ $cmsPageIds = array_column($neosNodes, 'cmsPageId');
+ $cmsPageId = $neosNodeEntity->getCmsPageId();
+ if (in_array($cmsPageId, $cmsPageIds, true)) {
continue;
}
- $cmsPageId = $neosNodeEntity->getCmsPageId();
if (in_array($cmsPageId, $defaultCmsPageIds)) {
throw new CanNotDeleteDefaultLayoutPageException(
@@ -228,38 +131,46 @@ public function removeCmsPagesWithInvalidNodeIdentifiers(array $neosNodes, Conte
);
}
- private function getCmsPageIdWithConnectedNodeIdentifier(Context $context): array
- {
- $neosNodeEntities = $this->getNeosNodeEntitiesWithConnectedCmsPage($context);
-
- $availableNodeIdentifiers = [];
- /** @var NeosNodeEntity $nlxNeosNodeEntity */
- foreach ($neosNodeEntities as $nlxNeosNodeEntity) {
- $availableNodeIdentifiers[$nlxNeosNodeEntity->getCmsPageId()] = $nlxNeosNodeEntity->getNodeIdentifier();
- }
-
- return $availableNodeIdentifiers;
- }
-
- private function removeAlreadyPresentNeosLayouts(array $pages, array $alreadyPresentNeosLayouts): array
- {
- return array_filter($pages, function ($page) use ($alreadyPresentNeosLayouts) {
- return !in_array($page['nodeIdentifier'], $alreadyPresentNeosLayouts);
- });
- }
-
public function createNotification(Context $context, ?string $message = null, ?string $status = 'error'): void
{
$this->notificationService->createNotification(
[
'id' => Uuid::randomHex(),
'requiredPrivileges' => [],
- 'message' => $message ?? $this->translator->trans('nlxNeosContent.notification.neosLayoutPagesFetchError'),
+ 'message' => $message ?? $this->translator->trans(
+ 'nlxNeosContent.notification.neosLayoutPagesFetchError'
+ ),
'status' => $status,
],
$context
);
}
+
+ public function processProvidedNodes(array $nodes, Context $context): void
+ {
+ $pagesData = [];
+ foreach ($nodes as $node) {
+ $cmsPageData = [
+ 'id' => $node['cmsPageId'],
+ 'name' => $node['title'],
+ 'type' => $node['type'],
+ 'nlxNeosNode' => [
+ 'id' => md5($node['cmsPageId'] . '_neos_node'),
+ 'versionId' => DEFAULTS::LIVE_VERSION,
+ 'cmsPageId' => $node['cmsPageId'],
+ 'cmsPageVersionId' => DEFAULTS::LIVE_VERSION,
+ 'neosConnection' => true,
+ ],
+ ];
+
+ $pagesData[] = $cmsPageData;
+ }
+
+ $this->cmsPageRepository->upsert(
+ $pagesData,
+ $context
+ );
+ }
}
diff --git a/src/Storefront/Controller/CmsSectionController.php b/src/Storefront/Controller/CmsSectionController.php
index d2d5579..083cbcb 100644
--- a/src/Storefront/Controller/CmsSectionController.php
+++ b/src/Storefront/Controller/CmsSectionController.php
@@ -5,6 +5,7 @@
namespace nlxNeosContent\Storefront\Controller;
use Exception;
+use nlxNeosContent\Core\Content\Cms\Aggregate\CmsSection\NeosCmsSectionEntity;
use nlxNeosContent\Service\ContentExchangeService;
use nlxNeosContent\Service\ResolverContextService;
use Shopware\Core\Content\Category\CategoryDefinition;
@@ -26,6 +27,7 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
+use Symfony\Component\Serializer\SerializerInterface;
/**
* @internal
@@ -44,6 +46,7 @@ function __construct(
private readonly ContentExchangeService $contentExchangeService,
private readonly ResolverContextService $resolverContextService,
private readonly EntityRepository $cmsPageRepository,
+ private readonly SerializerInterface $serializer,
) {
}
@@ -114,12 +117,11 @@ public function cmsSection(
true
);
+ $sectionData['id'] = 'neos-preview-section';
+
/** @var CmsSectionEntity $section */
- $section = $this->contentExchangeService->createCmsSectionFromSectionData(
- $sectionData,
- 1,
- 'section'
- );
+ $section = $this->serializer->denormalize($sectionData, NeosCmsSectionEntity::class, 'json');
+ $section->setPosition(1);
foreach ($section->getBlocks() as $block) {
$block->setSection($section);
diff --git a/src/Storefront/Controller/NeosPageController.php b/src/Storefront/Controller/NeosPageController.php
new file mode 100644
index 0000000..197e8b2
--- /dev/null
+++ b/src/Storefront/Controller/NeosPageController.php
@@ -0,0 +1,56 @@
+contentExchangeService->fetchCmsSectionsFromNeosByPath(
+ $request->getPathInfo(),
+ $salesChannelContext
+ );
+ } catch (ClientException $e) {
+ if ($e->getCode() === 404) {
+ throw $this->createNotFoundException(previous: $e);
+ } else {
+ throw $e;
+ }
+ }
+
+ $resolverContext = $this->resolverContextService->getResolverContextForEntityNameAndId(
+ CategoryDefinition::ENTITY_NAME,
+ '019a7c22388f72efbf7c5219977ba492',
+ $salesChannelContext,
+ $request
+ );
+ $this->contentExchangeService->loadSlotData($sections->getBlocks(), $resolverContext);
+ $cmsPage = new CmsPageEntity();
+ $cmsPage->setSections($sections);
+
+
+ return $this->renderStorefront('@Storefront/storefront/page/neosPage.html.twig', [
+ 'cmsPage' => $cmsPage,
+ 'landingPage' => []
+ ]);
+ }
+}