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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Changelog
## Unreleased
### Add
- Add SsrSearchResultsReceivedEvent to RecordList
## [v6.5.1] - 2026.01.27
### Change
- Upgrade Web Components version to v5.1.8
Expand Down
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,27 @@ Without SSR enabled, web crawlers could not have a chance to scan the element re

**Note:** If you have a problem with displaying product images or prices correctly, you probably have Field Roles set incorrectly. You can easily fix this by setting [custom fields roles](#set-custom-field-roles)

##### SSR Record List Data Event

When Server Side Rendering (SSR) is enabled, the plugin renders the ff-record-list content on the server before sending the HTML response to the client.
To allow flexible customization of the rendered data, the plugin dispatches a dedicated Symfony event that enables developers to modify, enrich or filter the SSR result set.

This event is especially useful for:

- Enriching product data with custom attributes.
- Modifying record lists depending on user session or context.
- Filtering or reordering products before rendering.
- Injecting additional metadata used by frontend components.

Applying business rules that cannot be easily implemented on the frontend.

**SsrSearchResultsReceivedEvent** is dispatched right before the SSR HTML output is generated.
It provides full access to:
- The product record list data.
- The current Shopware context.
- The current HTTP request and user session.

This allows deep customization of rendered results. (Check example implementation)[]

## Features Settings

Expand Down Expand Up @@ -669,6 +690,41 @@ class EnrichProxyDataEventSubscriber implements EventSubscriberInterface

```

### Enrich SSR record list data by adding a custom flag to each product.

```php
<?php

declare(strict_types=1);

namespace Omikron\FactFinder\Shopware6\Subscriber;

use Omikron\FactFinder\Shopware6\Events\SsrSearchResultsReceivedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class EnrichSsrRecordListEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [SsrSearchResultsReceivedEvent::class => 'enrichData'];
}

public function enrichData(SsrSearchResultsReceivedEvent $event): void
{
$data = $event->getData();
$records = $data['records'] ?? [];

foreach ($records as &$record) {
if (isset($record['record']) && is_array($record['record'])) {
$record['record']['custom_badge'] = 'recommended';
}
}

$data['records'] = $records;
$event->setData($data);
}
}
```

## Contribute

Expand Down
8 changes: 5 additions & 3 deletions spec/Storefront/Controller/ResultControllerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Shopware\Storefront\Framework\Routing\RequestTransformer;
use Shopware\Storefront\Page\GenericPageLoader;
use Shopware\Storefront\Page\Page;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
Expand Down Expand Up @@ -78,15 +79,15 @@ public function let(
$this->container->get(SeoUrlPlaceholderHandlerInterface::class)->willReturn($seoUrlPlaceholderHandler);
$this->container->get(MediaUrlPlaceholderHandlerInterface::class)->willReturn($mediaUrlPlaceholderHandler);
$this->container->get('twig')->willReturn($twig);
$this->setTwig($twig);
$nestedEventDispatcher->dispatch(Argument::any())->willReturn(Argument::any());
$this->setContainer($container);
}

public function it_should_return_original_response_content_when_ssr_is_not_active(
SearchAdapter $searchAdapter,
Page $page,
Engine $handlebars
Engine $handlebars,
EventDispatcherInterface $eventDispatcher
): void {
$content = 'original content';
$this->pageLoader->load($this->request, $this->salesChannelContext)->willReturn($page);
Expand All @@ -98,7 +99,8 @@ public function it_should_return_original_response_content_when_ssr_is_not_activ
$this->request,
$this->salesChannelContext,
$searchAdapter,
$handlebars
$handlebars,
$eventDispatcher
);

$response->shouldBeAnInstanceOf(Response::class);
Expand Down
29 changes: 29 additions & 0 deletions src/Events/SsrSearchResultsReceivedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Omikron\FactFinder\Shopware6\Events;

use Symfony\Contracts\EventDispatcher\Event;

class SsrSearchResultsReceivedEvent extends Event
{
public const NAME = 'factfinder.ssr.search_results.received';

private array $data;

public function __construct(array $data)
{
$this->data = $data;
}

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

public function setData(array $data): void
{
$this->data = $data;
}
}
6 changes: 6 additions & 0 deletions src/Storefront/Controller/ResultController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Controller\StorefrontController;
use Shopware\Storefront\Page\GenericPageLoader;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
#[Route(defaults: ['_routeScope' => ['storefront']])]
class ResultController extends StorefrontController
{
Expand All @@ -35,6 +39,7 @@ public function result(
SalesChannelContext $context,
SearchAdapter $searchAdapter,
Engine $handlebars,
EventDispatcherInterface $eventDispatcher,
): Response {
$page = $this->pageLoader->load($request, $context);
$response = $this->renderStorefront('@Parent/storefront/page/factfinder/result.html.twig', ['page' => $page]);
Expand All @@ -48,6 +53,7 @@ public function result(
$handlebars,
$searchAdapter,
$this->config,
$eventDispatcher,
$context->getSalesChannelId(),
$response->getContent(),
);
Expand Down
17 changes: 11 additions & 6 deletions src/Subscriber/CategoryPageResponseSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -31,6 +32,7 @@ class CategoryPageResponseSubscriber implements EventSubscriberInterface
private SearchAdapter $searchAdapter;
private Engine $handlebars;
private CategoryPath $categoryPath;
private EventDispatcherInterface $eventDispatcher;

public function __construct(
bool $httpCacheEnabled,
Expand All @@ -39,13 +41,15 @@ public function __construct(
SearchAdapter $searchAdapter,
Engine $handlebars,
CategoryPath $categoryPath,
EventDispatcherInterface $eventDispatcher,
) {
$this->httpCacheEnabled = $httpCacheEnabled;
$this->categoryRepository = $categoryRepository;
$this->config = $config;
$this->searchAdapter = $searchAdapter;
$this->handlebars = $handlebars;
$this->categoryPath = $categoryPath;
$this->httpCacheEnabled = $httpCacheEnabled;
$this->categoryRepository = $categoryRepository;
$this->config = $config;
$this->searchAdapter = $searchAdapter;
$this->handlebars = $handlebars;
$this->categoryPath = $categoryPath;
$this->eventDispatcher = $eventDispatcher;
}

public static function getSubscribedEvents()
Expand Down Expand Up @@ -79,6 +83,7 @@ public function onPageRendered(ResponseEvent $event): void
$this->handlebars,
$this->searchAdapter,
$this->config,
$this->eventDispatcher,
$request->attributes->get('sw-sales-channel-id'),
$response->getContent(),
);
Expand Down
20 changes: 14 additions & 6 deletions src/Utilites/Ssr/Template/RecordList.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
namespace Omikron\FactFinder\Shopware6\Utilites\Ssr\Template;

use Omikron\FactFinder\Shopware6\Config\Communication;
use Omikron\FactFinder\Shopware6\Events\SsrSearchResultsReceivedEvent;
use Omikron\FactFinder\Shopware6\Utilites\Ssr\Exception\DetectRedirectException;
use Omikron\FactFinder\Shopware6\Utilites\Ssr\SearchAdapter;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;

class RecordList
Expand All @@ -21,21 +23,24 @@ class RecordList
private string $content;
private string $template;
private Communication $pluginConfig;
private EventDispatcherInterface $eventDispatcher;

public function __construct(
Request $request,
Engine $handlebars,
SearchAdapter $searchAdapter,
Communication $pluginConfig,
EventDispatcherInterface $eventDispatcher,
string $salesChannelId,
string $content,
) {
$this->request = $request;
$this->handlebars = $handlebars;
$this->searchAdapter = $searchAdapter;
$this->salesChannelId = $salesChannelId;
$this->content = $content;
$this->pluginConfig = $pluginConfig;
$this->request = $request;
$this->handlebars = $handlebars;
$this->searchAdapter = $searchAdapter;
$this->salesChannelId = $salesChannelId;
$this->content = $content;
$this->pluginConfig = $pluginConfig;
$this->eventDispatcher = $eventDispatcher;
$this->setTemplateString();
}

Expand All @@ -49,6 +54,9 @@ public function getContent(
bool $isNavigationRequest = false,
): string {
$results = $this->searchResults($paramString, $isNavigationRequest);
$event = new SsrSearchResultsReceivedEvent($results);
$this->eventDispatcher->dispatch($event);
$results = $event->getData();

// Support redirect campaigns for SSR
if ($this->getRedirectCampaign($results)) {
Expand Down
Loading