integration_documentation:plugin:en:integration:shopware_5:extension

Action disabled: source
integration_documentation:plugin:en:integration:shopware_5:extension

Shopware 5 plugin extension

The Findologic extension plugin for Shopware 5 allows manual adaptions to the behavior of the main Findologic plugin.

It's possible override, extend and replace components, to fit the needs of your store. The most common use case is to add additional data to the Findologic product export.

  1. Download the latest zip file from our GitHub release page.

  2. Open the Plugin Manager in the Shopware backend.

  3. Click Upload plugin and select downloaded zip file.

Decorators

Adaptions need to be done using Symfony decorators.

By default the extension plugin decorates the FindologicArticleFactory which is responsible for creating new FindologicArticleModel instances. Those products are used by the export to build an XML.

Resources/services.xml

<?xml version="1.0" encoding="utf-8"?>
<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="extend_fin_search_unified.article_model_factory"
            class="ExtendFinSearchUnified\BusinessLogic\FindologicArticleFactory"
            decorates="fin_search_unified.article_model_factory"
        >
        </service>
    </services>
</container>

FindologicArticleFactory

With the decoration in place, the plugin returns an extended FindologicArticleModel instance also defined in the extension plugin.

BusinessLogic/FindologicArticleFactory.php

<?php
 
namespace ExtendFinSearchUnified\BusinessLogic;
 
use ExtendFinSearchUnified\BusinessLogic\Models\FindologicArticleModel;
use Shopware\Models\Article\Article;
use Shopware\Models\Category\Category;
 
class FindologicArticleFactory
{
    public function create(Article $shopwareArticle, $shopKey, array $allUserGroups, array $salesFrequency, Category $baseCategory)
    {
        return new FindologicArticleModel($shopwareArticle, $shopKey, $allUserGroups, $salesFrequency, $baseCategory);
    }
}

FindologicArticleModel

This class can be used to customize functionality of the main plugin.

BusinessLogic\Models\FindologicArticleModel.php

<?php
 
namespace ExtendFinSearchUnified\BusinessLogic\Models;
 
use Exception;
use FINDOLOGIC\Export\Data\Property;
use FinSearchUnified\BusinessLogic\Models\FindologicArticleModel as OriginalFindologicArticleModel;
use FinSearchUnified\Helper\StaticHelper;
use Shopware\Bundle\MediaBundle\MediaService;
use Shopware\Models\Article\Article;
use Shopware\Models\Article\Detail;
use Shopware\Models\Article\Image;
use Shopware\Models\Category\Category;
use Shopware\Models\Media\Media;
 
class FindologicArticleModel extends OriginalFindologicArticleModel
{
    // The variant column that differentiates the different variants
    const VARIANT_TYPE = 'Farbe';
 
    const URL_REPLACEMENTS = [
        '[' => '%5B',
        ']' => '%5D'
    ];
 
    /**
     * @var MediaService|null
     */
    protected $mediaService;
 
    public function __construct(
        Article $shopwareArticle,
        $shopKey,
        array $allUserGroups,
        array $salesFrequency,
        Category $baseCategory
    ) {
        parent::__construct($shopwareArticle, $shopKey, $allUserGroups, $salesFrequency, $baseCategory);
 
//        $this->mediaService = Shopware()->Container()->get('shopware_media.media_service');
//
//        if ($this->legacyStruct) {
//            $this->addVariantsJson();
//        }
    }
 
    //...
}

Examples

You can manually add attributes and properties by adding them to $this->xmlArticle with the help of libflexport.

BusinessLogic\Models\FindologicArticleModel.php

use FINDOLOGIC\Export\Data\Attribute;
use FINDOLOGIC\Export\Data\Property;
 
// ...
public function setAttributes(): void
{
    parent::setAttributes();
 
    $this->xmlArticle->addAttribute(
        new Attribute(
            'filter_name',
            ['value1', 'value2']
        )
    );
}
 
public function setProperties(): void
{
    parent::setProperties();
 
    $this->xmlArticle->addProperty(
        new Property('new_property', ['' => 'I am a property value!'])
    );
}
// ...

The part ['' => 'I am a property value!'] has an empty string as array index. An empty string as an array key, simply means that there is no usergroup, as property data can be usergroup-specific.

You can read more about usergroups in the libflexport documentation, which is the library used to build the export xml.

In case you have usergroup-specific data, properties which should be available for all usergroups, must be added individually for each usergroup.

The default attributes export contains the free text field from attr1 to attr20. Additional and custom free text fields can be exported using the example below.

use FINDOLOGIC\Export\Data\Attribute;
use FINDOLOGIC\Export\Data\Property;
 
public function __construct(
    Article $shopwareArticle,
    $shopKey,
    array $allUserGroups,
    array $salesFrequency,
    Category $baseCategory
) {
    parent::__construct($shopwareArticle, $shopKey, $allUserGroups, $salesFrequency, $baseCategory);
 
    if ($this->legacyStruct) {
        $this->addAdditionalFreeTextFields();
    }
}
 
// ...
public function addAdditionalFreeTextFields(): void
{
    $freeTextFields = $this->baseVariant->getAttribute();
 
    // Add free text field as property (without usergroup)
    if(is_callable([$freeTextFields, 'getCustomRatingCount'])) {
        $customRatingCount = $freeTextFields->getCustomRatingCount();
        if (!StaticHelper::isEmpty($customRatingCount)) {
            $this->xmlArticle->addProperty(
                new Property('rating_count', ['' => StaticHelper::cleanString($customRatingCount)])
            );
        }
    }
 
    // Add free text field as attribute
    if(is_callable([$freeTextFields, 'getCustomRatingAvg'])) {
        $customRatingAvg = $freeTextFields->getCustomRatingAvg();
        if (!StaticHelper::isEmpty($customRatingAvg)) {
            $this->xmlArticle->addAttribute(
                new Attribute('rating_avg', [StaticHelper::cleanString($customRatingAvg)])
            );
        }
    }
}
// ...

The function names are dynamically generated by Shopware. They are accessible as PascalCase, prefixed with "get". So the getter for the free text field custom_rating_count, is generated as getCustomRatingCount.

The default extension plugin already includes the code to export variants as a JSON string, the provided code just needs a small adaption:

  1. Check out the variant configuration and get the property name, which defines your variants.

  2. Configure the variant name in the constant VARIANT_TYPE (line 19)

  3. Comment out the line 40-44

     //        $this->mediaService = Shopware()->Container()->get('shopware_media.media_service');
     //
     //        if ($this->legacyStruct) {
     //            $this->addVariantsJson();
     //        }

The exported variant JSON could look like:

{
    "red": {
        "productUrl": "https://www.example.com/de/jacket?number=SW10000-01",
        "images": [
            "https://www.example.com/media/image/73/bc/ef/jacket-red-01.jpg",
            "https://www.example.com/media/image/73/bc/ef/jacket-red-02.jpg",
        ],
        "thumbnails": [
            "https://www.example.com/media/image/73/bc/ef/jacket-red-01_200x200.jpg",
            "https://www.example.com/media/image/73/bc/ef/jacket-red-02_200x200.jpg",
        ],
    },
    "black": {
        "productUrl": "https://www.example.com/de/jacket?number=SW10000-02",
        "images": [
            "https://www.example.com/media/image/73/bc/ef/jacket-black-01.jpg",
            "https://www.example.com/media/image/73/bc/ef/jacket-black-02.jpg",
        ],
        "thumbnails": [
            "https://www.example.com/media/image/73/bc/ef/jacket-black-01_200x200.jpg",
            "https://www.example.com/media/image/73/bc/ef/jacket-black-02_200x200.jpg",
        ],
    }
}
Requires Findologic Plugin v11.5.2 or above

The Findologic plugin already provides sorting options for the most common use-cases. The plugin uses SortingHandler to send the currently selected sorting option via API parameters to the Findologic Search-API (see all available SortingHandlers).

To handle custom sorting options, create a custom SortingHandler in the extension plugin, and override the responsible QueryBuilderFactory to include the created SortingHandler.

Supported Shopware 5 sortings by default:

  • Release date
  • Popularity
  • Product name
  • Price
  • Relevance

Prerequisites

Before a custom sorting can be used, make sure to export the value for the custom sort in the <sort> field in the export. See the XML Format documentation for further details.

Implementation

Example implementation for sorting according to the stock value. This can be done for any non-supported sorting option, or with some sorting from a third-party plugin.

Step 1: Export the sort value

The export of the stock value needs to be added to ExtendFinSearchUnified/BusinessLogic/Models/FindologicArticleModel.php

<?php
 
namespace ExtendFinSearchUnified\BusinessLogic\Models;
 
use FINDOLOGIC\Export\Data\Sort;
use FinSearchUnified\BusinessLogic\Models\FindologicArticleModel as OriginalFindologicArticleModel;
use Shopware\Models\Article\Article;
use Shopware\Models\Category\Category;
 
class FindologicArticleModel extends OriginalFindologicArticleModel
{
    public function __construct(
        Article $shopwareArticle,
        $shopKey,
        array $allUserGroups,
        array $salesFrequency,
        Category $baseCategory
    ) {
        parent::__construct($shopwareArticle, $shopKey, $allUserGroups, $salesFrequency, $baseCategory);
 
        $this->setSort();
    }
 
    public function setSort()
    {
        $sort = new Sort();
        $sort->setValue($this->baseVariant->getInStock());
 
        $this->xmlArticle->setSort($sort);
    }
}

Step 2: Find the correct Shopware/Third-party sorting class

The available Shopware sorting classes can be found here. This is needed for the compatibility check inside the SortingHandler.

Using ProductStockSorting for this example.

Step 2: Create a SortingHandler

Create the folder structure Bundle/SearchBundleFindologic/SortingHandler in the extension plugin, and add a custom sorting handler class. In this example it will be ProductStockSortingHandler:

<?php
 
namespace ExtendFinSearchUnified\Bundle\SearchBundleFindologic\SortingHandler;
 
use FinSearchUnified\Bundle\SearchBundleFindologic\QueryBuilder\QueryBuilder;
use FinSearchUnified\Bundle\SearchBundleFindologic\SortingHandlerInterface;
use Shopware\Bundle\SearchBundle\Sorting\ProductStockSorting;
use Shopware\Bundle\SearchBundle\SortingInterface;
use Shopware\Bundle\StoreFrontBundle\Struct\ShopContextInterface;
 
class ProductStockSortingHandler implements SortingHandlerInterface
{
    /**
     * Checks if the passed sorting can be handled by this class
     *
     * @param SortingInterface $sorting
     *
     * @return bool
     */
    public function supportsSorting(SortingInterface $sorting)
    {
        return $sorting instanceof ProductStockSorting;
    }
 
    /**
     * Handles the passed sorting object.
     *
     * @param SortingInterface $sorting
     * @param QueryBuilder $query
     * @param ShopContextInterface $context
     */
    public function generateSorting(SortingInterface $sorting, QueryBuilder $query, ShopContextInterface $context)
    {
        /** @var ProductStockSorting $sorting */
        $query->addOrder('shopsort ' . $sorting->getDirection());
    }
}

Step 3: Decorate the QueryBuilderFactory

The QueryBuilderFactory registers all the available sorting options. Add your custom sort to this factory. Create the folder structure Bundle/SearchBundleFindologic/QueryBuilder in the extension plugin, and add the file QueryBuilderFactory. Override the registerSortingHandlers method and add your handler to the array.

<?php
 
namespace ExtendFinSearchUnified\Bundle\SearchBundleFindologic\QueryBuilder;
 
use ExtendFinSearchUnified\Bundle\SearchBundleFindologic\SortingHandler\ProductStockSortingHandler;
use FinSearchUnified\Bundle\SearchBundleFindologic\QueryBuilder\QueryBuilderFactory as OriginalQueryBuilderFactory;
use FinSearchUnified\Bundle\SearchBundleFindologic\SortingHandlerInterface;
 
class QueryBuilderFactory extends OriginalQueryBuilderFactory
{
    /**
     * @return SortingHandlerInterface[]
     */
    protected function registerSortingHandlers()
    {
        $sortingHandlers = parent::registerSortingHandlers();
 
        $sortingHandlers[] = new ProductStockSortingHandler();
 
        return $sortingHandlers;
    }
}

Step 4: Add the decorated factory to the services.xml

Finally decorate it in your Resources/services.xml:

<?xml version="1.0" encoding="utf-8"?>
<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="extend_fin_search_unified.article_model_factory"
            class="ExtendFinSearchUnified\BusinessLogic\FindologicArticleFactory"
            decorates="fin_search_unified.article_model_factory"
        >
        </service>
 
        <service
            id="extend_fin_search_unified.query_builder_factory"
            class="ExtendFinSearchUnified\Bundle\SearchBundleFindologic\QueryBuilder\QueryBuilderFactory"
            decorates="fin_search_unified.query_builder_factory"
        >
            <argument type="service" id="shopware_plugininstaller.plugin_manager" />
            <argument type="service" id="config" />
        </service>
    </services>
</container>

Once this step is done, selecting your relevant sorting option will send the order parameter to the Findologic API.