<?php declare(strict_types=1);
namespace Shopware\Storefront\Controller;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\Error\Error;
use Shopware\Core\Checkout\Cart\Error\ErrorRoute;
use Shopware\Core\Content\Seo\SeoUrlPlaceholderHandlerInterface;
use Shopware\Core\Framework\Adapter\Twig\TemplateFinder;
use Shopware\Core\Framework\Feature;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Routing\RequestTransformerInterface;
use Shopware\Core\Framework\Script\Execution\Hook;
use Shopware\Core\Framework\Script\Execution\ScriptExecutor;
use Shopware\Core\PlatformRequest;
use Shopware\Core\Profiling\Profiler;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Event\StorefrontRenderEvent;
use Shopware\Storefront\Framework\Routing\RequestTransformer;
use Shopware\Storefront\Framework\Routing\Router;
use Shopware\Storefront\Framework\Routing\StorefrontResponse;
use Shopware\Storefront\Framework\Twig\Extension\IconCacheTwigFilter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
use Twig\Environment;
#[Package('storefront')]
abstract class StorefrontController extends AbstractController
{
public const SUCCESS = 'success';
public const DANGER = 'danger';
public const INFO = 'info';
public const WARNING = 'warning';
private Environment $twig;
public function setTwig(Environment $twig): void
{
$this->twig = $twig;
}
protected function renderStorefront(string $view, array $parameters = []): Response
{
$request = $this->container->get('request_stack')->getCurrentRequest();
if ($request === null) {
$request = new Request();
}
$salesChannelContext = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
/* @feature-deprecated $view will be original template in StorefrontRenderEvent from 6.5.0.0 */
if (Feature::isActive('FEATURE_NEXT_17275')) {
$event = new StorefrontRenderEvent($view, $parameters, $request, $salesChannelContext);
} else {
$inheritedView = $this->getTemplateFinder()->find($view);
$event = new StorefrontRenderEvent($inheritedView, $parameters, $request, $salesChannelContext);
}
$this->container->get('event_dispatcher')->dispatch($event);
$iconCacheEnabled = $this->getSystemConfigService()->get('core.storefrontSettings.iconCache');
/** @deprecated tag:v6.5.0 - icon cache will be true by default. */
if ($iconCacheEnabled || (Feature::isActive('v6.5.0.0') && $iconCacheEnabled === null)) {
IconCacheTwigFilter::enable();
}
$response = Profiler::trace('twig-rendering', function () use ($view, $event) {
return $this->render($view, $event->getParameters(), new StorefrontResponse());
});
/** @deprecated tag:v6.5.0 - icon cache will be true by default. */
if ($iconCacheEnabled || (Feature::isActive('v6.5.0.0') && $iconCacheEnabled === null)) {
IconCacheTwigFilter::disable();
}
if (!$response instanceof StorefrontResponse) {
throw new \RuntimeException('Symfony render implementation changed. Providing a response is no longer supported');
}
$host = $request->attributes->get(RequestTransformer::STOREFRONT_URL);
$seoUrlReplacer = $this->container->get(SeoUrlPlaceholderHandlerInterface::class);
$content = $response->getContent();
if ($content !== false) {
$response->setContent(
$seoUrlReplacer->replace($content, $host, $salesChannelContext)
);
}
$response->setData($parameters);
$response->setContext($salesChannelContext);
$response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, '1');
$response->headers->set('Content-Type', 'text/html');
return $response;
}
protected function trans(string $snippet, array $parameters = []): string
{
return $this->container
->get('translator')
->trans($snippet, $parameters);
}
protected function createActionResponse(Request $request): Response
{
if ($request->get('redirectTo') || $request->get('redirectTo') === '') {
$params = $this->decodeParam($request, 'redirectParameters');
$redirectTo = $request->get('redirectTo');
if ($redirectTo) {
return $this->redirectToRoute($redirectTo, $params);
}
return $this->redirectToRoute('frontend.home.page', $params);
}
if ($request->get('forwardTo')) {
$params = $this->decodeParam($request, 'forwardParameters');
return $this->forwardToRoute($request->get('forwardTo'), [], $params);
}
return new Response();
}
protected function forwardToRoute(string $routeName, array $attributes = [], array $routeParameters = []): Response
{
$router = $this->container->get('router');
$url = $this->generateUrl($routeName, $routeParameters, Router::PATH_INFO);
// for the route matching the request method is set to "GET" because
// this method is not ought to be used as a post passthrough
// rather it shall return templates or redirects to display results of the request ahead
$method = $router->getContext()->getMethod();
$router->getContext()->setMethod(Request::METHOD_GET);
$route = $router->match($url);
$router->getContext()->setMethod($method);
$request = $this->container->get('request_stack')->getCurrentRequest();
if ($request === null) {
$request = new Request();
}
$attributes = array_merge(
$this->container->get(RequestTransformerInterface::class)->extractInheritableAttributes($request),
$route,
$attributes,
['_route_params' => $routeParameters]
);
return $this->forward($route['_controller'], $attributes, $routeParameters);
}
protected function decodeParam(Request $request, string $param): array
{
$params = $request->get($param);
if (\is_string($params)) {
$params = json_decode($params, true);
}
if (empty($params)) {
$params = [];
}
return $params;
}
protected function addCartErrors(Cart $cart, ?\Closure $filter = null): void
{
$errors = $cart->getErrors();
if ($filter !== null) {
$errors = $errors->filter($filter);
}
$groups = [
'info' => $errors->getNotices(),
'warning' => $errors->getWarnings(),
'danger' => $errors->getErrors(),
];
$request = $this->container->get('request_stack')->getMainRequest();
$exists = [];
if ($request && $request->hasSession() && method_exists($session = $request->getSession(), 'getFlashBag')) {
$exists = $session->getFlashBag()->peekAll();
}
$flat = [];
foreach ($exists as $messages) {
$flat = array_merge($flat, $messages);
}
/** @var array<string, Error[]> $groups */
foreach ($groups as $type => $errors) {
foreach ($errors as $error) {
$parameters = [];
foreach ($error->getParameters() as $key => $value) {
$parameters['%' . $key . '%'] = $value;
}
if ($error->getRoute() instanceof ErrorRoute) {
$parameters['%url%'] = $this->generateUrl(
$error->getRoute()->getKey(),
$error->getRoute()->getParams()
);
}
$message = $this->trans('checkout.' . $error->getMessageKey(), $parameters);
if (\in_array($message, $flat, true)) {
continue;
}
$this->addFlash($type, $message);
}
}
}
protected function renderView(string $view, array $parameters = []): string
{
$view = $this->getTemplateFinder()->find($view);
if (isset($this->twig)) {
return $this->twig->render($view, $parameters);
}
Feature::triggerDeprecationOrThrow(
'v6.5.0.0',
sprintf('Class %s does not have twig injected. Add to your service definition a method call to setTwig with the twig instance', static::class)
);
return parent::renderView($view, $parameters);
}
protected function getTemplateFinder(): TemplateFinder
{
return $this->container->get(TemplateFinder::class);
}
protected function hook(Hook $hook): void
{
$this->container->get(ScriptExecutor::class)->execute($hook);
}
protected function getSystemConfigService(): SystemConfigService
{
return $this->container->get(SystemConfigService::class);
}
}