Custum field einzelner Produkte prüfen im Checkout

Hallo zusammen,

Gibt es eine Möglichkeit im Checkout Prozess (im besten Falle im confirm tpl) einen Text anzuzeigen in Abhängigkeit davon ob bestimmte Produkte (gesetztes Custom Field am Produkt) im Warenkorb sind oder nicht?

Im confirm.tpl habe ich nur das page Object zur Verfügung, damit komme ich aber nicht weiter.

Ich versuche gerade Backendseitig das Cart Object mit an das tpl zu geben, das sieht auch erstmal gut aus. Und scheint mein Problem zu lösen.
Allerdings habe ich Testweise den original Shopware CheckoutController verändert. Das funktioniert, aber so kann es natürlich nicht bleiben.

Kann ich die Funktionen des CheckoutControllers in einem Plugin überschreiben?
Ich versuche das gerade, komme aber nicht weit. Anleitungen und Tutorials beschränken sich fast nur auf neue eigene Controller schreiben.

So sieht mein neuer Controller aus:

<?php declare(strict_types=1);

namespace customplugin\src\Controller;

use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\Error\Error;
use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
use Shopware\Core\Checkout\Cart\Exception\InvalidCartException;
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
use Shopware\Core\Checkout\Order\Exception\EmptyCartException;
use Shopware\Core\Checkout\Order\SalesChannel\OrderService;
use Shopware\Core\Checkout\Payment\Exception\InvalidOrderException;
use Shopware\Core\Checkout\Payment\Exception\PaymentProcessException;
use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException;
use Shopware\Core\Checkout\Payment\PaymentService;
use Shopware\Core\Framework\Feature;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Shopware\Core\Framework\Routing\Annotation\Since;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
use Shopware\Core\Profiling\Profiler;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Checkout\Cart\Error\PaymentMethodChangedError;
use Shopware\Storefront\Checkout\Cart\Error\ShippingMethodChangedError;
use Shopware\Storefront\Controller\StorefrontController;
use Shopware\Storefront\Framework\AffiliateTracking\AffiliateTrackingListener;
use Shopware\Storefront\Framework\Routing\Annotation\NoStore;
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedHook;
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoader;
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedHook;
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoader;
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedHook;
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoader;
use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutInfoWidgetLoadedHook;
use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutOffcanvasWidgetLoadedHook;
use Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPageLoader;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;

/**
 * @Route(defaults={"_routeScope"={"storefront"}})
 *
 * @deprecated tag:v6.5.0 - reason:becomes-internal - Will be internal
 */
#[Package('storefront')]
class CustomPluginCheckoutController extends CheckoutController
{
    private const REDIRECTED_FROM_SAME_ROUTE = 'redirected';

    private CartService $cartService;

    private CheckoutCartPageLoader $cartPageLoader;

    private CheckoutConfirmPageLoader $confirmPageLoader;

    private CheckoutFinishPageLoader $finishPageLoader;

    private OrderService $orderService;

    private PaymentService $paymentService;

    private OffcanvasCartPageLoader $offcanvasCartPageLoader;

    private SystemConfigService $config;

    private AbstractLogoutRoute $logoutRoute;

    /**
     * @internal
     */
    public function __construct(
        CartService $cartService,
        CheckoutCartPageLoader $cartPageLoader,
        CheckoutConfirmPageLoader $confirmPageLoader,
        CheckoutFinishPageLoader $finishPageLoader,
        OrderService $orderService,
        PaymentService $paymentService,
        OffcanvasCartPageLoader $offcanvasCartPageLoader,
        SystemConfigService $config,
        AbstractLogoutRoute $logoutRoute
    ) {
        $this->cartService = $cartService;
        $this->cartPageLoader = $cartPageLoader;
        $this->confirmPageLoader = $confirmPageLoader;
        $this->finishPageLoader = $finishPageLoader;
        $this->orderService = $orderService;
        $this->paymentService = $paymentService;
        $this->offcanvasCartPageLoader = $offcanvasCartPageLoader;
        $this->config = $config;
        $this->logoutRoute = $logoutRoute;
    }

    /**
     * @Since("6.0.0.0")
     * @NoStore
     * @Route("/checkout/confirm", name="frontend.checkout.confirm.page", options={"seo"="false"}, methods={"GET"}, defaults={"XmlHttpRequest"=true})
     */
    public function confirmPage(Request $request, SalesChannelContext $context): Response
    {
        if (!$context->getCustomer()) {
            return $this->redirectToRoute('frontend.checkout.register.page');
        }

        if ($this->cartService->getCart($context->getToken(), $context)->getLineItems()->count() === 0) {
            return $this->redirectToRoute('frontend.checkout.cart.page');
        }

        $page = $this->confirmPageLoader->load($request, $context);
        $cart = $page->getCart();
        $cartErrors = $cart->getErrors();

        $lineItems = $cart->getLineItems()->getFlat();

        $this->hook(new CheckoutConfirmPageLoadedHook($page, $context));

        $this->addCartErrors($cart);

        if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
            $cartErrors->clear();

            // To prevent redirect loops add the identifier that the request already got redirected from the same origin
            return $this->redirectToRoute(
                'frontend.checkout.confirm.page',
                \array_merge(
                    $request->query->all(),
                    [self::REDIRECTED_FROM_SAME_ROUTE => true]
                ),
            );
        }

        return $this->renderStorefront('@Storefront/storefront/page/checkout/confirm/index.html.twig', ['page' => $page, 'cart' => $cart, 'lineItems' => $lineItems]);
    }
}

So sieht meine routes.xml aus:

<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
        https://symfony.com/schema/routing/routing-1.0.xsd">

    <import resource="../../Controller/CheckoutController.php"/>
</routes>

und so meine services.xml:

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="customplugin\Storefront\Controller\CustomPluginCheckoutController">
            <argument type="service" id="Shopware\Core\Checkout\Cart\SalesChannel\CartService"/>
            <argument type="service" id="Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoader"/>
            <argument type="service" id="Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoader"/>
            <argument type="service" id="Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoader"/>
            <argument type="service" id="Shopware\Core\Checkout\Order\SalesChannel\OrderService"/>
            <argument type="service" id="Shopware\Core\Checkout\Payment\PaymentService"/>
            <argument type="service" id="Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPageLoader"/>
            <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
            <argument type="service" id="Shopware\Core\Checkout\Customer\SalesChannel\LogoutRoute"/>
            <call method="setContainer">
                <argument type="service" id="service_container"/>
            </call>
            <call method="setTwig">
                <argument type="service" id="twig"/>
            </call>
        </service>
    </services>
</container>

Da sagt er mir allerdings:

Compile Error: Cannot declare class customplugin\src\Controller\CustomPluginCheckoutController, because the name is already in use

Was mache ich hier falsch? Oder ist es keine gute Idee den Checkout-Controller zu erweitern?

Müsste ich eher über Event Subscriptions gehen? Das habe ich noch nicht en detail probiert.

Bin für Hilfe oder alternative Lösungen dankbar.

Ok, ich habe jetzt an anderer Stelle hier im Forum gelesen, dass meistens bei diesem Fehler eine Namespace Problematik besteht :thinking:. Aber eigentlich ist der Namespace im Controller der richtige Ort, wo der Controller auch liegt.

Edit:
Also der es liegt definitiv an meiner routing.xml
hier scheint er Probleme zu haben ich habe jetzt noch den Typ zu annotations ergänzt

    <import resource="../../Controller/*" type="annotation"/>

bekomme aber jetzt diesen netten Fehler

Class pluginname\src\Controller\CustomPluginCheckoutController does not exist in 
/srv/www/htdocs/shop/custom/plugins/pluginname/src/Controller/CustomPluginCheckoutController.php 
(which is being imported from "/srv/www/htdocs/shop/custom/plugins/pluginname/src/Resources/config/routes.xml"). 
Make sure to use PHP 8+ or that annotations are installed and enabled.

Ich bin ein bisschen Ratlos, die Klasse gibt es, sie ist dort wo sie sein soll :thinking:

Niemand eine Idee?
Es muss doch möglich sein, eine Shopware Standard Controller Action zu überschreiben. Ich komme einfach nicht drauf wo hier der Fehler liegt? :pensive:

Falls es nochmal jemand brauchen sollte.
Ich habe hier:
Function eines Controller überschreiben viele wichtige Hinweise gefunden.
Für mich sind die wichtigsten Punkte / Lerneffekte ähnlich.

Vor allem beim Namespace muss man aufpassen, das hier alles korrekt ist, das war bei mir das Größte Problem. Weitere wichtige Punkte stehen im verlinkten Post.

Einen Controller wirklich zu überschreiben, ist hier vermutlich nur selten notwendig. Viel einfacher ist es auf ein Event wie „StorefrontRenderEvent“ zu subscriben. Hier kann man dann Daten in alle oder einzelne Seite „injecten“.

Danke für den Hinweis, wie würde das konkret aussehen? Mir ist noch nicht ganz klar wie ich das korrekte Event identifziere bzw. wie ich innerhalb des Events an das Cart komme. Im StorefrontRenderEvent kommt das cart nicht mit, so wie ich das sehe.