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') }} -
- - - -
-{% 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' => [] + ]); + } +}