如何在Shopware 6商店前端显示额外的菜单树?

huangapple go评论78阅读模式
英文:

How to show additional menu trees in Shopware 6 Storefront?

问题

我尝试将额外的菜单添加到商店前端。

我们有一个包含链接的三列页脚菜单,按三个主题排序。

  • 链接到法律页面
  • 链接到客户支持主题
  • 关于我们的信息

现在我想要在页面标题中构建一个下拉菜单,只显示三个类别中的两个,并且应该有两个级别深度。

我无法在这里使用页脚服务导航,因为它是硬编码的,只能显示一个导航级别,而我已经在另一个菜单中使用了这个导航。

因此,我创建了一个额外的顶级类别树,它将只包含具有指向页脚导航中条目的内部链接的子类别。

到目前为止,我已经做了什么:

我在我的主题中注册了一个服务

    <services>
        <service id="MyExample\Subscriber\HeaderPageletLoadedSubscriber">
            <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
            <argument type="service" id="Shopware\Core\Content\Category\Service\NavigationLoader"/>
            <tag name="kernel.event_subscriber"/>
        </service>
    </services>

然后,我创建了一个订阅者,注册了HeaderPageletLoadedEvent::class,并加载了我在主题插件选项中配置的类别。

<?php declare(strict_types=1);

namespace MyExample\Subscriber;

use Shopware\Core\Content\Category\Service\NavigationLoaderInterface;
use Shopware\Core\Content\Category\Tree\Tree;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Pagelet\Header\HeaderPageletLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class HeaderPageletLoadedSubscriber implements EventSubscriberInterface
{
    private NavigationLoaderInterface $navigationLoader;
    private SystemConfigService $systemConfigService;
    public function __construct(
        SystemConfigService $systemConfigService,
        NavigationLoaderInterface $navigationLoader
    )
    {
        $this->systemConfigService = $systemConfigService;
        $this->navigationLoader = $navigationLoader;
    }

    public static function getSubscribedEvents(): array
    {
        // 返回要监听的事件,形式为数组: <要监听的事件> => <要执行的方法>
        return [
            HeaderPageletLoadedEvent::class => 'onHeaderPageletLoaded'
        ];
    }

    public function onHeaderPageletLoaded(HeaderPageletLoadedEvent $event)
    {
        $context = $event->getSalesChannelContext();

        $request = $event->getRequest();

        $serviceTree = $this->getMenuTreeByKey($context, $request);

        if ($serviceTree) {
            $event->getPagelet()->addExtension('MyMenu', $serviceTree);
        }
    }

    /**
     * @param SalesChannelContext $salesChannelContext
     * @param $request
     * @return Tree
     */
    private function getMenuTreeByKey(SalesChannelContext $salesChannelContext, $request): Tree
    {
        $salesChannelId = $salesChannelContext->getSalesChannelId();
        $channelId = $this->systemConfigService->get('MyExample.config.serviceRoot', $salesChannelId);

        $tree = null;
        if ($channelId) {
            $navigationId = $request->get('navigationId', $channelId);

            $tree = $this->navigationLoader->load($navigationId, $salesChannelContext, $channelId, 2);
        }
        return $tree;
    }
}

只有在我选择一个也设置为Shopware 6管理中的销售渠道入口点的类别时,我的代码才能正常工作。如果我选择了入口点之外的类别,我会收到CategoryNotFoundException

这似乎与Shopware论坛上报告的问题相同。

在商店前端加载类别树的正确方法是什么,以避免在/src/Core/Content/Category/SalesChannel/NavigationRoute.php中的validate()函数阻止?

英文:

I try to add additional menues to the storefront.

We have a three column footer menu with links sorted in three topics.

  • links to legal pages
  • links to customer support topics
  • about us kind of information

I now want to build a dropdown menu for the page header, that will show only two of the three categories and should be two levels deep.

I can not use the footer service navigation for this because it is hard coded to only show one level of navigation and I already use this navigation for another menu.

So I created a additional top-level-category tree that will only contain child categories with internal links to entries in the footer navigation.

What I have done so far:

I register a service in my theme

    &lt;services&gt;
        &lt;service id=&quot;MyExample\Subscriber\HeaderPageletLoadedSubscriber&quot;&gt;
            &lt;argument type=&quot;service&quot; id=&quot;Shopware\Core\System\SystemConfig\SystemConfigService&quot;/&gt;
            &lt;argument type=&quot;service&quot; id=&quot;Shopware\Core\Content\Category\Service\NavigationLoader&quot;/&gt;
            &lt;tag name=&quot;kernel.event_subscriber&quot;/&gt;
        &lt;/service&gt;
    &lt;/services&gt;

Then I create a Subscriber that registers on HeaderPageletLoadedEvent::class and loads a category that I have configured in my themes plugin options.

&lt;?php declare(strict_types=1);

namespace MyExample\Subscriber;

use Shopware\Core\Content\Category\Service\NavigationLoaderInterface;
use Shopware\Core\Content\Category\Tree\Tree;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Pagelet\Header\HeaderPageletLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class HeaderPageletLoadedSubscriber implements EventSubscriberInterface
{
    private NavigationLoaderInterface $navigationLoader;
    private SystemConfigService $systemConfigService;
    public function __construct(
        SystemConfigService $systemConfigService,
        NavigationLoaderInterface $navigationLoader
    )
    {
        $this-&gt;systemConfigService = $systemConfigService;
        $this-&gt;navigationLoader = $navigationLoader;
    }

    public static function getSubscribedEvents(): array
    {
        // Return the events to listen to as array like this:  &lt;event to listen to&gt; =&gt; &lt;method to execute&gt;
        return [
            HeaderPageletLoadedEvent::class =&gt; &#39;onHeaderPageletLoaded&#39;
        ];
    }

    public function onHeaderPageletLoaded(HeaderPageletLoadedEvent $event)
    {
        $context = $event-&gt;getSalesChannelContext();

        $request = $event-&gt;getRequest();

        $serviceTree = $this-&gt;getMenuTreeByKey($context, $request);

        if ( $serviceTree ) {
            $event-&gt;getPagelet()-&gt;addExtension(&#39;MyMenu&#39;, $serviceTree);
        }
    }

    /**
     * @param SalesChannelContext $salesChannelContext
     * @param $request
     * @return Tree
     */
    private function getMenuTreeByKey(SalesChannelContext $salesChannelContext, $request): Tree
    {
        $salesChannelId = $salesChannelContext-&gt;getSalesChannelId();
        $channelId = $this-&gt;systemConfigService-&gt;get(&#39;MyExample.config.serviceRoot&#39;, $salesChannelId);

        $tree = null;
        if ($channelId) {
            $navigationId = $request-&gt;get(&#39;navigationId&#39;, $channelId);

            $tree = $this-&gt;navigationLoader-&gt;load($navigationId, $salesChannelContext, $channelId, 2);
        }
        return $tree;

    }
}

My code only works as long as I choose a category, that is also set as an Sales-Channel entry point in the Shopware 6 admin. If I select a category outside the entry-points I get a CategoryNotFoundException

This seems to be the same issue like reported on the Shopware forums

What is the proper way to load a category tree for the storefront, that will not be blocked by the validate() function in /src/Core/Content/Category/SalesChannel/NavigationRoute.php?

答案1

得分: 0

加载程序检查类别是否是当前销售渠道入口点之一或它们的后代之一。这是有意的,因为任何不在入口点树之外的类别都不在当前销售渠道的范围之内。即使您设法获得它们各自的树,超出范围的类别也将无法访问。

您没有将类别设置为入口点的后代的原因是因为您不希望它显示在主导航中吗?您可以通过首先将类别设置为导航中隐藏来解决这个问题。

但是,这也会导致在检索时该类别不再是树的一部分。您可以注入NavigationRoute而不是NavigationLoaderInterface。您还需要category.repository来获取根类别,如果您不仅需要其子类。

然后,您可以使用该路由从自定义根ID获取所有类别。您还需要将请求参数buildTree设置为true,以便您获取一个包含子类别的类别集合,直到设置的级别。

<service id="MyExample\Subscriber\HeaderPageletLoadedSubscriber">
    <argument type="service" id="category.repository"/>
    <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
    <argument type="service" id="Shopware\Core\Content\Category\SalesChannel\NavigationRoute"/>
</service>

然后,您可以使用以下代码根据键获取菜单树:

private function getMenuTreeByKey(SalesChannelContext $salesChannelContext, $request): ?CategoryCollection
{
    $salesChannelId = $salesChannelContext->getSalesChannelId();
    $channelId = $this->systemConfigService->get('MyExample.config.serviceRoot', $salesChannelId);

    $tree = null;
    if ($channelId) {
        $navigationId = $request->get('navigationId', $channelId);

        $request->query->set('buildTree', 'true');
        $request->query->set('depth', '2');
        $navigationId = $request->get('navigationId', $channelId);

        /** @var CategoryEntity $category */
        $category = $this->repository->search(new Criteria([$navigationId]), $salesChannelContext->getContext())->first();

        $tree = $this->navigationRoute
            ->load($navigationId, $navigationId, $request, $salesChannelContext, new Criteria())
            ->getCategories();

        $tree = $category->setChildren($tree);
    }

    return $tree;
}
英文:

The loader checks if the category either is one of the current sales channels entry points or one of their descendants. This is intended, as any categories outside of entry point trees are out of scope of the current sales channel. Even if you managed to get their respective trees, the out-of-scope categories wouldn't be accessible.

Was the reason you didn't set up the category as one of the descendants of the entry point because you didn't want it to show up in the main navigation? You could work around that by first setting the category to be hidden in the navigation.

However this will also cause the category no longer be part of the tree when you retrieve it. Instead of injecting the NavigationLoaderInterface you can inject NavigationRoute instead. You'll also need category.repository to fetch the root category if you not only need its children.

&lt;service id=&quot;MyExample\Subscriber\HeaderPageletLoadedSubscriber&quot;&gt;
    &lt;argument type=&quot;service&quot; id=&quot;category.repository&quot;/&gt;
    &lt;argument type=&quot;service&quot; id=&quot;Shopware\Core\System\SystemConfig\SystemConfigService&quot;/&gt;
    &lt;argument type=&quot;service&quot; id=&quot;Shopware\Core\Content\Category\SalesChannel\NavigationRoute&quot;/&gt;
&lt;/service&gt;

Then you can use the route to get all the categories descending from your custom root id. You also need to set the request param buildTree to true so you get a category collection with the children being available up to the set level.

private function getMenuTreeByKey(SalesChannelContext $salesChannelContext, $request): ?CategoryCollection
{
    $salesChannelId = $salesChannelContext-&gt;getSalesChannelId();
    $channelId = $this-&gt;systemConfigService-&gt;get(&#39;MyExample.config.serviceRoot&#39;, $salesChannelId);

    $tree = null;
    if ($channelId) {
        $navigationId = $request-&gt;get(&#39;navigationId&#39;, $channelId);
        
        $request-&gt;query-&gt;set(&#39;buildTree&#39;, &#39;true&#39;);
        $request-&gt;query-&gt;set(&#39;depth&#39;, &#39;2&#39;);
        $navigationId = $request-&gt;get(&#39;navigationId&#39;, $channelId);
        
        /** @var CategoryEntity $category */
        $category = $this-&gt;repository-&gt;search(new Criteria([$navigationId]), $salesChannelContext-&gt;getContext())-&gt;first();
        
        $tree = $this-&gt;navigationRoute
            -&gt;load($navigationId, $navigationId, $request, $salesChannelContext, new Criteria())
            -&gt;getCategories();

        $tree = $category-&gt;setChildren($tree);
    }
    
    return $tree;
}

huangapple
  • 本文由 发表于 2023年6月27日 17:28:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76563472.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定