Skip to content

Commit 2a768fc

Browse files
authored
Merge pull request #66 from netlogix/feat/keep-context-when-switching-language
feat: Implement caching for Neos page tree and enhance routing with l…
2 parents 8103e61 + f819180 commit 2a768fc

7 files changed

Lines changed: 253 additions & 3 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "netlogix/neos-content",
33
"description": "This plugin enables Shopware templates to be designed with an Enterprise CMS.",
4-
"version": "0.1.44",
4+
"version": "0.1.45",
55
"type": "shopware-platform-plugin",
66
"license": "MIT",
77
"autoload": {

src/Factory/NeosPageTreeItemFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private function createCategoryEntity(NeosPageDTO $page): CategoryEntity
5050
//TODO figure out child count and visible child count and level
5151

5252
$category = new SalesChannelCategoryEntity();
53-
$category->setId($page->identifier);
53+
$category->setId(str_replace('-', '', $page->identifier));
5454
$category->setName($page->label);
5555
$category->setType('neos-entrypoint');
5656
$category->setSeoUrl(

src/Neos/Endpoint/AbstractNeosPageTreeLoader.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
readonly abstract class AbstractNeosPageTreeLoader
1111
{
12-
abstract function getDecorated(): AbstractNeosPageTreeLoader;
12+
public function getDecorated(): AbstractNeosPageTreeLoader {
13+
return $this;
14+
}
1315
abstract function load(SalesChannelContext $salesChannelContext): NeosPageCollection;
1416
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace nlxNeosContent\Neos\Endpoint;
6+
7+
use nlxNeosContent\Neos\DTO\NeosPageCollection;
8+
use Psr\Log\LoggerInterface;
9+
use Shopware\Core\System\SalesChannel\SalesChannelContext;
10+
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
11+
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
12+
use Symfony\Contracts\Cache\CacheInterface;
13+
14+
#[AsDecorator(NeosPageTreeLoader::class)]
15+
readonly class CachedNeosPageTreeLoader extends AbstractNeosPageTreeLoader
16+
{
17+
private const CACHE_KEY = 'netlogix_neos_content_neos_page_tree';
18+
private const CACHE_TTL = 86400;
19+
20+
public function __construct(
21+
#[AutowireDecorated]
22+
private AbstractNeosPageTreeLoader $decorated,
23+
private CacheInterface $cache,
24+
private LoggerInterface $logger,
25+
) {
26+
27+
}
28+
29+
function load(SalesChannelContext $salesChannelContext): NeosPageCollection
30+
{
31+
try {
32+
return $this->cache->get(
33+
self::CACHE_KEY,
34+
function() use ($salesChannelContext) {
35+
return $this->decorated->load($salesChannelContext);
36+
},
37+
self::CACHE_TTL
38+
);
39+
} catch (\Throwable $e) {
40+
$this->logger->error($e);
41+
return new NeosPageCollection();
42+
}
43+
}
44+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
4+
declare(strict_types=1);
5+
6+
namespace nlxNeosContent\Service;
7+
8+
use nlxNeosContent\Neos\DTO\NeosPageCollection;
9+
use nlxNeosContent\Neos\Endpoint\AbstractNeosPageTreeLoader;
10+
use Shopware\Core\System\SalesChannel\SalesChannelContext;
11+
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
12+
use Symfony\Component\HttpFoundation\Request;
13+
14+
#[Autoconfigure(public: true)]
15+
class NeosPageTreeService
16+
{
17+
function __construct(
18+
private readonly AbstractNeosPageTreeLoader $neosPageTreeLoader,
19+
) {
20+
}
21+
22+
public function findNodeIdentifierForRequestAndContext(Request $request, SalesChannelContext $salesChannelContext)
23+
{
24+
$neosPageTree = $this->neosPageTreeLoader->load($salesChannelContext);
25+
$pathInfo = $request->getPathInfo();
26+
27+
return $this->findByPathInfoInTree($pathInfo, $neosPageTree);
28+
}
29+
30+
public function findByPathInfoInTree(string $pathInfo, NeosPageCollection $tree)
31+
{
32+
foreach ($tree as $treeItem) {
33+
if (trim($pathInfo, '/') === trim($treeItem->path, '/')) {
34+
return $treeItem;
35+
}
36+
37+
$foundTreeItem = $this->findByPathInfoInTree($pathInfo, $treeItem->children);
38+
39+
if ($foundTreeItem !== null) {
40+
return $foundTreeItem;
41+
}
42+
}
43+
44+
return null;
45+
}
46+
47+
public function findPathInfoForIdentifierAndContext($nodeIdentifier, SalesChannelContext $salesChannelContext): string
48+
{
49+
$neosPageTree = $this->neosPageTreeLoader->load($salesChannelContext);
50+
51+
return $this->findPathInfoByNodeIdentifier($nodeIdentifier, $neosPageTree);
52+
}
53+
54+
public function findPathInfoByNodeIdentifier(string $nodeIdentifier, NeosPageCollection $tree): string
55+
{
56+
foreach ($tree as $treeItem) {
57+
if ($nodeIdentifier === str_replace('-', '', $treeItem->identifier)) {
58+
return $treeItem->path;
59+
}
60+
61+
$pathInfo = $this->findPathInfoByNodeIdentifier($nodeIdentifier, $treeItem->children);
62+
63+
if ($pathInfo !== '') {
64+
return $pathInfo;
65+
}
66+
}
67+
68+
return '';
69+
}
70+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace nlxNeosContent\Storefront\Controller;
6+
7+
use nlxNeosContent\Service\NeosPageTreeService;
8+
use Shopware\Core\Framework\Routing\RoutingException;
9+
use Shopware\Core\Framework\Uuid\Uuid;
10+
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
11+
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
12+
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService;
13+
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface;
14+
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters;
15+
use Shopware\Core\System\SalesChannel\SalesChannel\AbstractContextSwitchRoute;
16+
use Shopware\Core\System\SalesChannel\SalesChannel\ContextSwitchRoute;
17+
use Shopware\Core\System\SalesChannel\SalesChannelContext;
18+
use Shopware\Storefront\Controller\ContextController;
19+
use Shopware\Storefront\Framework\Routing\RequestTransformer;
20+
use Shopware\Storefront\Framework\Routing\Router;
21+
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
22+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
23+
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
24+
use Symfony\Component\HttpFoundation\RedirectResponse;
25+
use Symfony\Component\HttpFoundation\Request;
26+
use Symfony\Component\HttpFoundation\RequestStack;
27+
use Symfony\Component\HttpFoundation\Response;
28+
use Symfony\Component\Routing\RouterInterface;
29+
30+
#[AsDecorator(ContextController::class)]
31+
class ContextControllerDecorator extends ContextController
32+
{
33+
public function __construct(
34+
#[Autowire(service: ContextSwitchRoute::class)]
35+
private readonly AbstractContextSwitchRoute $contextSwitchRoute,
36+
private readonly RequestStack $requestStack,
37+
private readonly RouterInterface $router,
38+
#[AutowireDecorated]
39+
private readonly ContextController $inner,
40+
private readonly NeosPageTreeService $neosPageTreeService,
41+
#[Autowire(service: SalesChannelContextService::class)]
42+
private readonly SalesChannelContextServiceInterface $salesChannelContextService,
43+
) {
44+
parent::__construct(
45+
$this->contextSwitchRoute,
46+
$this->requestStack,
47+
$this->router,
48+
);
49+
}
50+
51+
public function switchLanguage(Request $request, SalesChannelContext $context): RedirectResponse
52+
{
53+
54+
if(array_key_exists('neos', $request->request->all('redirectParameters')) && $request->request->all('redirectParameters')['neos'] === "1")
55+
{
56+
if (!array_key_exists('navigationId', $request->request->all('redirectParameters'))) {
57+
throw new \InvalidArgumentException('navigationId is required for neos redirects');
58+
}
59+
60+
$languageId = $request->request->get('languageId');
61+
if (!$languageId) {
62+
throw RoutingException::missingRequestParameter('languageId');
63+
}
64+
65+
if (!\is_string($languageId) || !Uuid::isValid($languageId)) {
66+
throw RoutingException::invalidRequestParameter('languageId');
67+
}
68+
69+
$newContext = $this->salesChannelContextService->get(
70+
new SalesChannelContextServiceParameters(
71+
salesChannelId: $context->getSalesChannelId(),
72+
token: $context->getToken(),
73+
languageId: $languageId,
74+
currencyId: $context->getCurrencyId(),
75+
domainId: $context->getDomainId(),
76+
originalContext: $context->getContext(),
77+
customerId: $context->getCustomer()?->getId(),
78+
imitatingUserId: $context->getImitatingUserId(),
79+
)
80+
);
81+
82+
$nodeIdentifier = $request->request->all('redirectParameters')['navigationId'];
83+
$pathInfo = $this->neosPageTreeService->findPathInfoForIdentifierAndContext($nodeIdentifier, $newContext);
84+
85+
try {
86+
$newTokenResponse = $this->contextSwitchRoute->switchContext(
87+
new RequestDataBag([SalesChannelContextService::LANGUAGE_ID => $languageId]),
88+
$context
89+
);
90+
} catch (ConstraintViolationException) {
91+
throw RoutingException::languageNotFound($languageId);
92+
}
93+
94+
$parsedUrl = parse_url($newTokenResponse->getRedirectUrl());
95+
96+
if (!$parsedUrl) {
97+
throw RoutingException::languageNotFound($languageId);
98+
}
99+
100+
$redirectRequest = Request::create($newTokenResponse->getRedirectUrl());
101+
102+
return new RedirectResponse(rtrim($redirectRequest->getUri(), '/') . '/' . ltrim($pathInfo, '/'), Response::HTTP_FOUND);
103+
104+
}
105+
return $this->inner->switchLanguage($request, $context);
106+
}
107+
}

src/Storefront/Controller/NeosPageController.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66

77
use GuzzleHttp\Exception\ClientException;
88
use nlxNeosContent\Service\ContentExchangeService;
9+
use nlxNeosContent\Service\NeosPageTreeService;
910
use nlxNeosContent\Service\ResolverContextService;
1011
use Shopware\Core\Content\Category\CategoryDefinition;
1112
use Shopware\Core\Content\Cms\CmsPageEntity;
1213
use Shopware\Core\System\SalesChannel\SalesChannelContext;
1314
use Shopware\Storefront\Controller\StorefrontController;
15+
use Shopware\Storefront\Page\GenericPageLoader;
16+
use Shopware\Storefront\Page\GenericPageLoaderInterface;
17+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1418
use Symfony\Component\HttpFoundation\Request;
1519
use Symfony\Component\HttpFoundation\Response;
1620

@@ -19,6 +23,9 @@ class NeosPageController extends StorefrontController
1923
function __construct(
2024
private readonly ContentExchangeService $contentExchangeService,
2125
private readonly ResolverContextService $resolverContextService,
26+
private readonly NeosPageTreeService $neosPageTreeService,
27+
#[Autowire(service: GenericPageLoader::class)]
28+
private readonly GenericPageLoaderInterface $genericPageLoader,
2229
) {
2330
}
2431

@@ -37,6 +44,7 @@ function index(Request $request, SalesChannelContext $salesChannelContext): Resp
3744
}
3845
}
3946

47+
4048
$resolverContext = $this->resolverContextService->getResolverContextForEntityNameAndId(
4149
entityName: CategoryDefinition::ENTITY_NAME,
4250
entityId: $salesChannelContext->getSalesChannel()->getNavigationCategoryId(),
@@ -47,8 +55,27 @@ function index(Request $request, SalesChannelContext $salesChannelContext): Resp
4755
$cmsPage = new CmsPageEntity();
4856
$cmsPage->setSections($sections);
4957

58+
$treeItem = $this->neosPageTreeService->findNodeIdentifierForRequestAndContext($request, $salesChannelContext);
59+
//Setting NavigationId so the navigation js can display the active page
60+
$identifier = str_replace(
61+
'-',
62+
'',
63+
$treeItem->identifier
64+
);
65+
$request = $this->container->get('request_stack')->getCurrentRequest();
66+
$request->attributes->set('navigationId', $identifier);
67+
$request->attributes->set('_route', 'frontend.navigation.page');
68+
$request->attributes->set('_route_params', [
69+
'neos' => "1",
70+
'navigationId' => $identifier,
71+
]);
72+
73+
$page = $this->genericPageLoader->load($request, $salesChannelContext);
74+
$page->getMetaInformation()->setMetaTitle($treeItem->label);
75+
//TODO add missing Metadata
5076

5177
return $this->renderStorefront('@Storefront/storefront/page/neosPage.html.twig', [
78+
'page' => $page,
5279
'cmsPage' => $cmsPage,
5380
'landingPage' => []
5481
]);

0 commit comments

Comments
 (0)