CMS Blöcke in TWIG Template nutzen?

Hallo,

ist es möglich, die vorgefertigten CMS-Blöcke (die man im Backend in den Erlebniswelten auswählen kann), auch direkt in einem TWIG Template zu nutzen?

Konkret würde ich gerne in meinem eigenen TWIG-Template für die „Homepage“ den cms-block-product-slider.html.twig nutzen. Wenn ich den allerdings mit

{% sw_include "@Storefront/storefront/block/cms-block-product-slider.html.twig" %}

einfüge, passiert logischerweise gar nichts, denn es fehlen die Konfigurationen, die man per Erlebniswelt vornehmen kann (welche Produkte, usw).

Mein Ziel ist es, auf der „Homepage“ Produkte mit einem bestimmten Tag anzuzeigen. Per Erlebniswelt würde man das wahrscheinlich über die „Dynamische Produktgruppe“ lösen. Ich würde es aber gerne direkt in meinem Theme im TWIG Template verankern.

Vielen Dank bereits im Voraus.

Hallo @zlep,

du kannst die Templates wie gewohnt inkludieren. Wichtig ist hierbei, dass du dem Slider die entsprechenden Konfigurationen mit gibst, siehe dir als Beispiel das „Page/product-detail/Cross-Stellung/tabs.html.twig“ Template an.

Die gewünschten Produkte usw. könntest du ggf. über einen Subscriber zuweisen.

vg

@abdullah
Super, vielen Dank. Das hilft schon mal ein Stück weiter.
Ich würde das CMS Element im Template also wie folgt einfügen

                 {% sw_include "@Storefront/storefront/element/cms-element-product-slider.html.twig" with {
                     sliderConfig: config,
                     element: {
                         'data': {
                             'products': {
                                 elements: item.getProducts()
                             }
                         },
                         type: 'product-slider'
                     }
                 } %}

und in einem Subscriber (der auf ein Event hört, z.B. CmsPageLoaded) ganz einfach meine Produkte laden.
Könntest du mir noch auf die Sprünge helfen, wie ich diese dann in die sliderConfig bekomme? Wahrscheinlich müssen sie in das „element“ Objekt, nehme ich an? Wie würde ich das im Subscriber genau bewerkstelligen?

Danke noch mal

@zlep, je nach Anwendungsfall würde ich den StorefrontRendererEvents verwenden. Hier kannst du das Event um Parameter erweitern, z. B. zlebProducts und kannst dann im Template mit zlebProducts auf den Parameter zugreifen. Wichtig wäre hier, dass deine Änderungen also das hinzufügen des Parameters nur bei bestimmten Views passiert, damit nicht auf jeder Seite der Code ausgeführt wird.

Statt item->getProducts() verwendest du dann zlebProducts z. B.

@abdullah
Super, vielen Dank für deine Unterstützung. Wirklich super. :slight_smile:

Das hier wäre wahrscheinlich genau das was ich suche?

Als route dann frontend.home, wenn ich es nur auf der Homepage benötige, damit es nur dort ausgeführt wird, meinst du, oder?

Schönes WE.

@zlep , genau so oder so ähnlich.
Wenn keine Einschränkung vorhanden ist, wird überall im Storefront dein Code ausgeführt, was zu Performance Einbußen und somit lange Ladezeiten führen kann.

@abdullah

Sorry, ich muss noch mal nachfragen. Ich habe nun Produkte in einem Parameter „test“ in einem „elements“ array:

Leider ist mir nun immer noch nicht ganz klar, wie ich diese in meinem Twig Template in den Product Slider bekomme, sprich in diese Config:

     {% sw_include "@Storefront/storefront/element/cms-element-product-slider.html.twig" with {
         sliderConfig: config,
         element: {
             'data': {
                 'products': {
                     elements: item.getProducts()
                 }
             },
             type: 'product-slider'
         }
     } %}

Wäre wirklich super nett von dir, wenn du mir noch mal auf die Sprünge helfen könntest.
Vielen Dank

Ah, ich denke, ich habe es. Hier mal meine Lösung. Vielleicht hilft es weiter.

…/Subscriber/StorefrontRenderer.php

class StorefrontRenderer implements EventSubscriberInterface
{
    private EntityRepositoryInterface $productRepository;

    public function __construct(EntityRepositoryInterface $productRepository)
    {
        $this->productRepository = $productRepository;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            StorefrontRenderEvent::class => "onStorefrontRender"
        ];
    }

    public function onStorefrontRender(StorefrontRenderEvent $event): void
    {

        $routeName = $event->getRequest()->attributes->get('_route');

        if ($routeName == "frontend.home.page") {

            $criteria = new Criteria();

            $criteria->addFilter(new EqualsFilter('name', 'Main product with properties '));
            $context = $event->getContext();
            $products = $this->productRepository->search($criteria, $context);
            
            $event->setParameter("test", $products);
        }

    }
}

In Twig dann ganz einfach:

{% sw_include "@Storefront/storefront/element/cms-element-product-slider.html.twig" with {
         sliderConfig: config,
         element: {
             'data': {
                 'products': {
                     elements: test
                 }
             },
             type: 'product-slider'
         }
     } %}

Was allerdings immer noch nicht passt, ist die Formatierung. Es werden einfach nur 5 Produkte nebeneinander angezeigt. Also nicht wirklich der Slider.

@zlep Versuch Mal Test nicht das EntitySearchResult zuzuweisen sondern die productcollection (getEntities()).

Ansonsten wie sieht denn deine config im Template aus?

@abdullah
Sorry, bin erst jetzt dazu gekommen.
Also, das Problem bleibt, dass auch wenn ich die ProductCollection zuweise, der Preis als auch das Artikelbild fehlen:

$products = $this->productRepository->search($criteria, $event->getContext())->getEntities();

In der ProductCollection ist nur das hier vorhanden:

Shopware\Core\Content\Product\ProductCollection {#7015 ▼
  #elements: array:5 [▼
    "06793e437ed4490a82d33223a36cf44f" => Shopware\Core\Content\Product\ProductEntity {#7021 ▼
      #parentId: "c7bca22753c84d08b6178a50052b4146"
      #childCount: null
      #autoIncrement: 16
      #taxId: "1a276c67ff9b4a48acb7851fab272842"
      #manufacturerId: "7f24e96676e944b0a0addc20d56728cb"
      #unitId: null
      #active: true
      #displayGroup: "af160339563fcd7ae2c9becf4cd95adb"
      #price: Shopware\Core\Framework\DataAbstractionLayer\Pricing\PriceCollection {#7025 ▶}
      #manufacturerNumber: null
      #ean: null
      #sales: 0
      #productNumber: "SWDEMO10007.4"
      #stock: 50
      #availableStock: 50
      #available: true
      #deliveryTimeId: null
      #deliveryTime: null
      #restockTime: null
      #isCloseout: false
      #purchaseSteps: 1
      #maxPurchase: null
      #minPurchase: 1
      #purchaseUnit: 1.0
      #referenceUnit: 1.0
      #shippingFree: true
      #purchasePrices: null
      #markAsTopseller: null
      #weight: null
      #width: null
      #height: null
      #length: null
      #releaseDate: DateTimeImmutable @1650533412 {#7024 ▶}
      #categoryTree: array:3 [▶]
      #streamIds: null
      #optionIds: array:1 [▶]
      #propertyIds: array:7 [▶]
      #name: null
      #keywords: null
      #description: null
      #metaDescription: null
      #metaTitle: null
      #packUnit: null
      #packUnitPlural: null
      #variantRestrictions: null
      #configuratorGroupConfig: null
      #mainVariantId: null
      #variation: []
      #tax: Shopware\Core\System\Tax\TaxEntity {#7033 ▶}
      #manufacturer: null
      #unit: null
      #prices: Shopware\Core\Content\Product\Aggregate\ProductPrice\ProductPriceCollection {#7023 ▶}
      #cheapestPrice: Shopware\Core\Content\Product\DataAbstractionLayer\CheapestPrice\CheapestPrice {#7216 ▶}
      #cover: null
      #parent: null
      #children: null
      #media: null
      #cmsPageId: null
      #cmsPage: null
      #slotConfig: null
      #searchKeywords: null
      #translations: null
      #categories: null
      #customFieldSets: null
      #tags: null
      #properties: null
      #options: null
      #configuratorSettings: null
      #categoriesRo: null
      #coverId: "683c3a0a0c26464fb65332d1a9adf7e2"
      #blacklistIds: null
      #whitelistIds: null
      #visibilities: null
      #tagIds: null
      #categoryIds: array:1 [▶]
      #productReviews: null
      #ratingAverage: null
      #mainCategories: null
      #seoUrls: null
      #orderLineItems: null
      #crossSellings: null
      #crossSellingAssignedProducts: null
      #featureSetId: null
      #featureSet: null
      #customFieldSetSelectionActive: null
      #customSearchKeywords: null
      #wishlists: null
      #canonicalProductId: null
      #canonicalProduct: null
      #cheapestPriceContainer: Shopware\Core\Content\Product\DataAbstractionLayer\CheapestPrice\CheapestPriceContainer {#7029 ▶}
      #streams: null
      #_uniqueIdentifier: "06793e437ed4490a82d33223a36cf44f"
      #versionId: "0fa91ce3e96a4bc2be4bd9ce752c3425"
      #translated: array:10 [▶]
      #createdAt: DateTimeImmutable @1650533421 {#7030 ▶}
      #updatedAt: DateTimeImmutable @1654328944 {#7031 ▶}
      -_entityName: "product"
      -_fieldVisibility: Shopware\Core\Framework\DataAbstractionLayer\FieldVisibility {#7018 ▶}
      #extensions: array:1 [▶]
      #id: "06793e437ed4490a82d33223a36cf44f"
      #customFields: []
    }
    "1a521184875f4eb2b1dace6ef903d852" => Shopware\Core\Content\Product\ProductEntity {#7163 ▶}
    "44a2bd78c9134014bebd2b326f2f2c18" => Shopware\Core\Content\Product\ProductEntity {#7178 ▶}
    "c7bca22753c84d08b6178a50052b4146" => Shopware\Core\Content\Product\ProductEntity {#7188 ▶}
    "cee29c2947d6400fb3319177d605bad8" => Shopware\Core\Content\Product\ProductEntity {#7198 ▶}
  ]
  #extensions: []
}

Für den Slider würde man aber noch {% set cover = product.cover.media %} für die box-standard.html.twig sowie {% set real = product.calculatedPrice %} für die price-unit.html.twig benötigen.

Ganz davon abgesehen, verstehe ich aber sowieso nicht, wieso die ProductCollection kein Preis (calculatedPrice) und Bild (cover.media) mitliefert? Wo bekomme ich diese Infos denn her?

Danke noch mal

Eine Collection ist nichts anderes als ein Array von Entities. Da werden keine Properties hinzugefügt oder entfernt.

Shopware Nutzt coverId um an das Cover zu gelangen.

Wenn dir etwas fehlt, dann musst du es mit $criteria->addAssociation(…) hinzufügen.

@zlep wie bereits @Max_Shop erwähnt hat, musst du die gewünschten Associations deinem Criteria hinzufügen. In Shopware werden die Associations standardmäßig nicht mit geladen.

@Max_Shop , @abdullah

Danke euch.
Das Cover-Bild konnte ich mit $criteria->addAssociation('cover'); nun mitgeben. Preise haben allerdings leider nicht funktioniert. War mir nicht ganz klar, wie ich den calculatedPrice mitgeben kann. Den gibt es nämlich nicht in Shopware\Core\Content\Product\ProductDefinition oder übersehe ich etwas?

Bin nun aber einen (ganz) anderen Weg gegangen. Und zwar ist im sales_channel.product.repository (anstatt dem normalen product.repository) schon alles mit dabei, was man benötigt. Siehe Shopware\Core\Content\Product\SalesChannel\SalesChannelProductDefinition.

Wichtig ist nur, dass das das SalesChannelRepositoryInterface (anstatt dem EntityRepositoryInterface) benötigt sowie aus dem Event den $event->getSalesChannelContext().

Nun scheint tatsächlich alles zu funktionieren.
Danke euch noch mal für eure Unterstützung.

Hm, ein bisschen eine Never-Ending-Story. Ein weiteres Problem ist aufgetaucht, und zwar, dass die Produkte manchmal angezeigt werden und manchmal nicht. Ich habe die Vermutung, dass es irgendwie mit dem Cache zu tun haben könnte bzw., dass die Search-Query zu lange dauert und die Seite bis dahin schon geladen wurde.

Mir fehlt gerade ein bisschen der Ansatz, das Problem zu lösen. Jemand noch eine Idee?

Es gibt ein CachedSalesChannelRepoitory. Versuche es mal damit (könnte ggf. leicht anders heißen).

@Max_Shop Klingt gut, leider kann ich den nicht finden:

Könntest du noch mal schauen, wie der genau heißen könnte bzw. wo ich den finde?
Thx

Schau mal, ob du damit etwas anfangen kannst. Ich weiß leider nicht, ob es auf deinen Kontext passt. Einfach mal versuchen: https://shopwarian.com/how-to-create-a-sales-channel-context-in-shopware-6/

@zlep, der PHP Teil sollte noch vor dem Rendern der Seite zu einem Ergebnis kommen, daher denke ich nicht, dass die Search-Query zu lange braucht.
Hast du mal versucht die Daten zu loggen (PHP-seitig und ggf. mit einem dump im Template)?

Ich denke nicht, dass Cache das Problem verursacht, wenn du den SalesChannelRepository verwendest. Meiner Meinung nach sollte das Problem wo anders liegen.

Zum Laden von (List)Produkten verwende ich die ProductListRoute, die macht nichts anderes als ein SalesChannelRepository zu benutzen.
Wenn du die Produkte einer bestimnten Kategorie laden möchtest und die Kategorie kennst, kannst du auch die (Cached)ProductListingRoute verwenden.

@abdullah
Ich lade Produkte mit einem bestimmten Tag:

 $criteria->addAssociation('tags');
 $criteria->addFilter(new EqualsFilter('tags.name', "meinTag"));

In der Produktionsumgebung werden die nicht zuverlässig geladen.
Der Filter per Name funktioniert hingegen problemlos:

$criteria->addFilter(new EqualsFilter('name', 'Produktname'));

Kann das tatsächlich mit den Tags zu tun haben?

@zlep
ich bin mir nicht mehr sicher, aber ich glaube ich hatte früher mal auch Probleme beim Vergleich vom Tag Namen.
Ich habe dann die Tag-ID verwendet. Bei der Verwendung der Tag-ID hatte ich dann keine Probleme mehr.