Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "netlogix/neos-content",
"description": "This plugin enables Shopware templates to be designed with an Enterprise CMS.",
"version": "0.1.47",
"version": "0.1.48",
"type": "shopware-platform-plugin",
"license": "MIT",
"autoload": {
Expand Down
65 changes: 64 additions & 1 deletion src/Command/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@

namespace nlxNeosContent\Command;

use nlxNeosContent\Core\Content\Admin\Dto\CacheInvalidationDto;
use nlxNeosContent\Service\CachingInvalidationService;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Uuid\Uuid;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;

Expand All @@ -26,11 +30,70 @@ public function __construct(

protected function configure(): void
{
$this->addArgument(
'type',
InputArgument::OPTIONAL,
sprintf('The cache to invalidate. One of: %s.', implode(', ', CacheInvalidationDto::TYPES)),
CacheInvalidationDto::TYPE_ALL
);

$this->addOption(
'id',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
"One or more layout (CMS page) UUIDs to invalidate, e.g. --id=<UUID> --id=<UUID>. Only applies to type '" . CacheInvalidationDto::TYPE_LAYOUTS . "' or '" . CacheInvalidationDto::TYPE_ALL . "'. If omitted, all layouts are invalidated."
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->cacheInvalidationService->invalidateCachesForNeosCmsPages(Context::createCLIContext());
$type = $input->getArgument('type');
$ids = $input->getOption('id');

if (!in_array($type, CacheInvalidationDto::TYPES, true)) {
$output->writeln(sprintf(
"<error>Invalid type '%s'. Allowed values: %s.</error>",
$type,
implode(', ', CacheInvalidationDto::TYPES)
));

return Command::FAILURE;
}

if ($ids !== [] && $type === CacheInvalidationDto::TYPE_NAVIGATION) {
$output->writeln(sprintf(
"<error>The --id option cannot be used with type '%s'.</error>",
CacheInvalidationDto::TYPE_NAVIGATION
));

return Command::FAILURE;
}

foreach ($ids as $id) {
if (!Uuid::isValid($id)) {
$output->writeln(sprintf("<error>Invalid UUID '%s' given for --id.</error>", $id));

return Command::FAILURE;
}
}

$context = Context::createCLIContext();

try {
if ($type === CacheInvalidationDto::TYPE_ALL || $type === CacheInvalidationDto::TYPE_LAYOUTS) {
$this->cacheInvalidationService->invalidateNeosCmsLayoutCaches($ids, $context);
$output->writeln('<info>Layout caches invalidated successfully.</info>');
}

if ($type === CacheInvalidationDto::TYPE_ALL || $type === CacheInvalidationDto::TYPE_NAVIGATION) {
$this->cacheInvalidationService->invalidateNavigationCaches();
$output->writeln('<info>Navigation caches invalidated successfully.</info>');
}
} catch (\Throwable $e) {
$output->writeln(sprintf('<error>Failed to invalidate caches: %s</error>', $e->getMessage()));

return Command::FAILURE;
}

return Command::SUCCESS;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

namespace nlxNeosContent\Core\Content\Admin\ApiRoutes\CacheInvalidation;

use nlxNeosContent\Core\Content\Admin\Dto\CacheInvalidationDto;
use Shopware\Core\Framework\Context;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[Autoconfigure(tags: ['controller.service_arguments'], public: true)]
abstract class AbstractCacheInvalidationRoute
{
abstract public function getDecorated(): AbstractCacheInvalidationRoute;

abstract public function load(Request $request, Context $context): Response;
abstract public function load(
CacheInvalidationDto $cacheInvalidationDto,
Context $context
): Response;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace nlxNeosContent\Core\Content\Admin\ApiRoutes\CacheInvalidation;

use nlxNeosContent\Core\Content\Admin\Dto\CacheInvalidationDto;
use nlxNeosContent\Service\CachingInvalidationService;
use Shopware\Core\Framework\Context;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Annotation\Route;

#[Route(defaults: ['_routeScope' => ['api']])]
Expand All @@ -25,9 +26,24 @@ public function getDecorated(): AbstractCacheInvalidationRoute
}

#[Route(path: '/api/_action/neos/clear-cache', name: 'api.neos.clear-cache', methods: ['POST'])]
public function load(Request $request, Context $context): Response
public function load(
#[MapRequestPayload(acceptFormat: 'json')]
CacheInvalidationDto $cacheInvalidationDto,
Context $context
): Response
{
$this->cacheInvalidationService->invalidateCachesForNeosCmsPages($context);
if ($cacheInvalidationDto->getType() === CacheInvalidationDto::TYPE_ALL) {
$this->cacheInvalidationService->invalidateNeosCmsLayoutCaches([], $context);
$this->cacheInvalidationService->invalidateNavigationCaches();
return new JsonResponse(['status' => 'Neos Content Cache invalidated']);
}

if ($cacheInvalidationDto->getType() === CacheInvalidationDto::TYPE_LAYOUTS) {
$this->cacheInvalidationService->invalidateNeosCmsLayoutCaches($cacheInvalidationDto->getData(), $context);
} elseif ($cacheInvalidationDto->getType() === CacheInvalidationDto::TYPE_NAVIGATION)
{
$this->cacheInvalidationService->invalidateNavigationCaches();
}

return new JsonResponse(['status' => 'Neos Content Cache invalidated']);
}
Expand Down
51 changes: 51 additions & 0 deletions src/Core/Content/Admin/Dto/CacheInvalidationDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace nlxNeosContent\Core\Content\Admin\Dto;


use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

readonly class CacheInvalidationDto
{
public const TYPE_ALL = 'all';
public const TYPE_LAYOUTS = 'layouts';
public const TYPE_NAVIGATION = 'navigation';

public const TYPES = [
self::TYPE_ALL,
self::TYPE_LAYOUTS,
self::TYPE_NAVIGATION,
];

function __construct(
#[Assert\NotBlank(message: "The request body must contain a field 'type' of type string.")]
#[Assert\Choice(choices: self::TYPES, message: "The field 'type' must be one of: {{ choices }}.")]
private string $type,
private ?array $data = null,
)
{
}

public function getType(): string
{
return $this->type;
}

public function getData(): ?array
{
return $this->data;
}

#[Assert\Callback]
public function validate(ExecutionContextInterface $context, $payload): void
{
if ($this->type === self::TYPE_LAYOUTS && $this->data === null) {
$context->buildViolation("The request body must contain a field 'data' when 'type' is 'layouts'.")
->atPath('data')
->addViolation();
}
}
}
13 changes: 9 additions & 4 deletions src/Neos/Endpoint/CachedNeosPageTreeLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@
use nlxNeosContent\Neos\DTO\NeosPageCollection;
use Psr\Log\LoggerInterface;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

#[AsDecorator(NeosPageTreeLoader::class)]
readonly class CachedNeosPageTreeLoader extends AbstractNeosPageTreeLoader
{
private const CACHE_KEY = 'netlogix_neos_content_neos_page_tree';
public const CACHE_KEY = 'netlogix_neos_content_neos_page_tree';
private const CACHE_TTL = 86400;

public function __construct(
#[AutowireDecorated]
private AbstractNeosPageTreeLoader $decorated,
private CacheInterface $cache,
#[Autowire(service: 'cache.object')]
private TagAwareCacheInterface $cache,
private LoggerInterface $logger,
) {

Expand All @@ -31,7 +34,9 @@ function load(SalesChannelContext $salesChannelContext): NeosPageCollection
try {
return $this->cache->get(
self::CACHE_KEY . $salesChannelContext->getLanguageId(),
function() use ($salesChannelContext) {
function (ItemInterface $item) use ($salesChannelContext) {
$item->tag(self::CACHE_KEY);

return $this->decorated->load($salesChannelContext);
},
self::CACHE_TTL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ Component.register('nlx-invalidate-cms-page-caches-button', {
methods: {
clearNeosPageCaches() {
this.isLoading = true;
this.nlxNeosContentApiService.clearNeosPageCaches().then((response) => {
this.nlxNeosContentApiService.clearNeosPageCaches({
'type': 'all'
}).then((response) => {
if (response.success) {
this.createNotificationSuccess({
title: this.$tc('nlx-invalidate-cms-page-caches-button.label'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ Shopware.Component.register('neos-index', {
};
},

watch: {
inactiveConfiguration(value) {
this.toggleContentScroll(value);
}
},

created() {
Shopware.Store.get('adminMenu').collapseSidebar();
window.addEventListener('message', (event) => {
Expand All @@ -81,11 +87,16 @@ Shopware.Component.register('neos-index', {

},

beforeUnmount() {
this.toggleContentScroll(false);
},

mounted() {
this.$nextTick(async () => {
await this.loadConfig();
const neosBaseUri = await this.nlxConfigService.getSetting('neosBaseUri');
this.inactiveConfiguration = !neosBaseUri;
this.toggleContentScroll(this.inactiveConfiguration);
if (this.inactiveConfiguration) {
// If Neos is not active, we load the Fillout registration script
const script = document.createElement('script');
Expand Down Expand Up @@ -236,6 +247,12 @@ Shopware.Component.register('neos-index', {
this.$router.push({
name: 'nlx.neos.settings.index',
})
},

toggleContentScroll(isInactive) {
const contentEl = document.querySelector('.sw-desktop__content');
if (!contentEl) return;
contentEl.classList.toggle('nlx-neos-inactive-configuration', isInactive);
}
}
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.sw-desktop__content {
.sw-desktop__content.nlx-neos-inactive-configuration {
overflow: scroll;
}
.neos,
Expand Down
46 changes: 29 additions & 17 deletions src/Service/CachingInvalidationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace nlxNeosContent\Service;


use nlxNeosContent\Neos\Endpoint\CachedNeosPageTreeLoader;
use Shopware\Core\Content\Category\SalesChannel\NavigationRoute;
use Shopware\Core\Framework\Adapter\Cache\CacheInvalidator;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
Expand All @@ -18,28 +20,38 @@ public function __construct(
) {
}

public function invalidateCachesForNeosCmsPages(Context $context): void
public function invalidateNeosCmsLayoutCaches(array $cmsPageIds, Context $context): void
{
$criteria = new Criteria();
$criteria->addAssociation('nlxNeosNode');
$criteria->addFilter(
new NotFilter(
NotFilter::CONNECTION_AND,
[
new EqualsFilter('nlxNeosNode.id', null),
]
)
);
$neosCmsPageIds = $this->cmsPageRepository->searchIds(
$criteria,
$context
);

$tags = array_map(fn (string $id) => 'cms-page-' . $id, $neosCmsPageIds->getIds());
if (empty($cmsPageIds)) {
$criteria = new Criteria();
$criteria->addAssociation('nlxNeosNode');
$criteria->addFilter(
new NotFilter(
NotFilter::CONNECTION_AND,
[
new EqualsFilter('nlxNeosNode.id', null),
]
)
);
$neosCmsPageIds = $this->cmsPageRepository->searchIds(
$criteria,
$context
);

$cmsPageIds = $neosCmsPageIds->getIds();
}

$tags = array_map(fn (string $id) => 'cms-page-' . $id, $cmsPageIds);

$this->cacheInvalidator->invalidate($tags);
$this->cacheInvalidator->invalidateExpired();
$this->cacheInvalidator->invalidate(['nlxNeosContent']);
}

public function invalidateNavigationCaches(): void
{
$this->cacheInvalidator->invalidate([NavigationRoute::ALL_TAG, CachedNeosPageTreeLoader::CACHE_KEY]);
$this->cacheInvalidator->invalidateExpired();
$this->cacheInvalidator->invalidate(['nlxNeosContent']);
}
}
5 changes: 3 additions & 2 deletions src/Service/NeosPageTreeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
namespace nlxNeosContent\Service;

use nlxNeosContent\Neos\DTO\NeosPageCollection;
use nlxNeosContent\Neos\DTO\NeosPageDTO;
use nlxNeosContent\Neos\Endpoint\AbstractNeosPageTreeLoader;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
Expand All @@ -19,15 +20,15 @@ function __construct(
) {
}

public function findNodeIdentifierForRequestAndContext(Request $request, SalesChannelContext $salesChannelContext)
public function findNodeIdentifierForRequestAndContext(Request $request, SalesChannelContext $salesChannelContext): ?NeosPageDTO
{
$neosPageTree = $this->neosPageTreeLoader->load($salesChannelContext);
$pathInfo = $request->getPathInfo();

return $this->findByPathInfoInTree($pathInfo, $neosPageTree);
}

public function findByPathInfoInTree(string $pathInfo, NeosPageCollection $tree)
public function findByPathInfoInTree(string $pathInfo, NeosPageCollection $tree): ?NeosPageDTO
{
foreach ($tree as $treeItem) {
if (trim($pathInfo, '/') === trim($treeItem->path, '/')) {
Expand Down