integration_documentation:plugin:en:integration:shopware_5:extension

integration_documentation:plugin:en:integration:shopware_5:extension

This is an old revision of the document!


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
  3. Click "Upload plugin" -> select downloaded zip file -> Click "Upload plugin" again

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();
//        }
    }
 
    /*
     * Example on how to add custom properties to the item
     */
    public function setProperties()
    {
        parent::setProperties();
    }
 
    /*
     * Direct Integration only:
     * Example on how to add a property with all variants as a JSON.
     * Each variant includes the corresponding URL and assigned images.
     *
     * Example:
     * {
     *   "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",
     *     ],
     *   }
     * }
     */
    protected function addVariantsJson()
    {
        $variants = [];
 
        /** @var Detail $variant */
        foreach ($this->variantArticles as $variant) {
            if (!$variant->getActive() ||
                count($variant->getConfiguratorOptions()) === 0 ||
                (Shopware()->Config()->get('hideNoInStock') && $variant->getInStock() < 1)
            ) {
                continue;
            }
 
            foreach ($variant->getConfiguratorOptions() as $option) {
                if (StaticHelper::isEmpty($option->getName()) ||
                    StaticHelper::isEmpty($option->getGroup()->getName())
                ) {
                    continue;
                }
 
                $variantType = StaticHelper::removeControlCharacters($option->getGroup()->getName());
                $variantValue = StaticHelper::removeControlCharacters($option->getName());
 
                if ($variantType === self::VARIANT_TYPE && !isset($variants[$variantValue])) {
                    $variants[$variantValue] = $this->getNewVariant($variant);
                }
            }
        }
 
        if (!StaticHelper::isEmpty($variants)) {
            $this->xmlArticle->addProperty(
                new Property('variants', ['' => json_encode($variants)])
            );
        }
    }
 
    /**
     * @param Detail $variant
     * @return array
     */
    protected function getNewVariant($variant)
    {
        return [
            'productUrl' => $this->getUrlByVariant($variant),
            'images' => $this->getVariantImages($variant),
            'thumbnails' => $this->getVariantImages($variant, true),
        ];
    }
 
    /**
     * @param Detail $variant
     * @param boolean $getThumbnail
     * @return string[]
     */
    protected function getVariantImages($variant, $getThumbnail = false)
    {
        $images = [];
 
        /** @var Image $image */
        foreach ($variant->getImages() as $image) {
            if (!$image->getParent() || $image->getParent()->getMedia() === null) {
                continue;
            }
 
            $imageUrl = $this->getImageUrlByImage($image->getParent(), $getThumbnail);
            if ($imageUrl) {
                $images[$image->getPosition()] = $imageUrl;
            }
        }
 
        return array_values($images);
    }
 
    /**
     * @param Image $image
     * @param boolean $getThumbnail
     * @return string|null
     */
    protected function getImageUrlByImage($image, $getThumbnail = false)
    {
        /** @var Image $imageRaw */
        $imageRaw = $image->getMedia();
        if (!($imageRaw instanceof Media) || $imageRaw === null) {
            return null;
        }
 
        try {
            $imageDetails = $imageRaw->getThumbnailFilePaths();
            $imageDefault = $imageRaw->getPath();
        } catch (Exception $ex) {
            // Entity removed
            return null;
        }
 
        if (count($imageDetails) > 0) {
            return $getThumbnail
                ? strtr($this->mediaService->getUrl(array_values($imageDetails)[0]), self::URL_REPLACEMENTS)
                : strtr($this->mediaService->getUrl($imageDefault), self::URL_REPLACEMENTS);
        }
 
        return null;
    }
 
    /**
     * @param Detail $variant
     * @return string
     */
    protected function getUrlByVariant($variant)
    {
        $baseLink = $this->getBaseLinkByVariant($variant);
 
        return Shopware()->Modules()->Core()->sRewriteLink($baseLink, $variant->getArticle()->getName());
    }
 
    /**
     * @param Detail $variant
     * @return string
     */
    protected function getBaseLinkByVariant($variant)
    {
        return sprintf(
            '%s?sViewport=detail&sArticle=%s&number=%s',
            Shopware()->Config()->get('baseFile'),
            $variant->getArticle()->getId(),
            $variant->getNumber()
        );
    }
}

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.

As stated here, due to a limitation of Shopware, only the free text field from attr1 to attr20 are exported by default. Additionally added free text fields need to be exported manually.

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
{
    $free_text_fields = $this->baseVariant->getAttribute();
 
    if(is_callable([$free_text_fields, 'getCustomRatingCount'])) {
        $customRatingCount = $free_text_fields->getCustomRatingCount();
        if (!StaticHelper::isEmpty($customRatingCount)) {
            $this->xmlArticle->addProperty(
                new Property('rating_count', ['' => StaticHelper::cleanString($customRatingCount)])
            );
        }
    }
 
    if(is_callable([$free_text_fields, 'getCustomRatingAvg'])) {
        $customRatingAvg = $free_text_fields->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, this 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();
     //        }

Example JSON

{
    "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",
        ],
    }
}