A basic export format plugin

In this tutorial, you will develop a basic plugin to integrate an export format in plentymarkets. The plugin will enable the user to export data using the well-known processes within the plentymarkets elastic export.

Step 1: Creating the plugin files

The plugin needs to be organised in a specific structure. You need the src folder for your plugin. The src folder can be organised in sub-folders and contains the ExportFormatServiceProvider.php, the ExportFormatGenerator.php and the ExportFormatResultField.php. The ExportFormatServiceProvider.php is needed for registering the plugin in plentymarkets. All information about the plugin is defined in the plugin.json file. This file defines the service provider of your plugin, which will be called by plentymarkets to register and run your plugin.

PluginExportFormatTutorial/
    ├── meta/
    │   ├── documents/
    │   │   └── changelog_de.md
    │   │   └── changelog_en.md
    │   │   └── user_guide_de.md
    │   │   └── user_guide_en.md
    │   │
    │   └── images/
    │       └── icon_author_md.png
    │       └── icon_author_sm.png
    │       └── icon_author_xs.png
    │       └── icon_plugin_md.png
    │       └── icon_plugin_sm.png
    │       └── icon_plugin_xs.png
    │
    ├── src/
    │   ├── Generator/
    │   │   └── ExportFormatGenerator.php
    │   │
    │   ├── ResultField/
    │   │   └── ExportFormatResultFields.php
    │   │
    │   └── ExportFormatServiceProvider.php
    │
    ├── LICENSE.json // license information
    ├── plugin.json // plugin information
    └── README.md // plugin README file

Step 2: Filling the source files

This plugin consists of four files in total. One JSON file, the plugin.json, and three PHP files, a ServiceProvider, a Generator and a ResultField. You will also need a config.json as well as a ServiceProvider. Create these files and copy the code examples. Start by creating the plugin.json file.

Code for the plugin.json

PluginExportFormatTutorial/plugin.json
{
    "name": "PluginExportFormatTutorial",
    "namespace": "PluginExportFormatTutorial",
    "type": "export",
    "version": "1.0.0",
    "license": "GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007",
    "pluginIcon": "icon_plugin_md.png",
    "price": 0.00,
    "description": "This plugin adds the format ExportFormat to the Elastic Export.",
    "shortDescription": {
        "de": "Ein einfaches Exportformat-Plugin.",
        "en": "A basic export format plugin."
    },
    "categories": [
        "3523"
    ],
    "marketplaceName": {
        "de":"Exportformat-Tutorial",
        "en":"Export format tutorial"
    },
    "require": [
        "ElasticExport"
    ],
    "author": "plentymarkets GmbH",
    "authorIcon": "icon_author_xs.png",
    "serviceProvider": "PluginExportFormatTutorial\\ExportFormatServiceProvider",
    "keywords": [
        "plugin",
        "export",
        "format",
        "tutorial"
    ]
}

Code for the ServiceProvider

PluginExportFormatTutorial/src/ExportFormatServiceProvider.php
<?php

namespace PluginExportFormatTutorial;

use Plenty\Modules\DataExchange\Services\ExportPresetContainer;
use Plenty\Plugin\ServiceProvider;

/**
 * Class ExportFormatServiceProvider
 * @package PluginExportFormatTutorial
 */
class ExportFormatServiceProvider extends ServiceProvider
{
    /**
     * Abstract function for registering the service provider.
     */
    public function register()
    {

    }

    /**
     * Adds the export format to the export container.
     *
     * @param ExportPresetContainer $container
     */
    public function boot(ExportPresetContainer $container)
    {
        $container->add(
            'ExportFormat',
            'PluginExportFormatTutorial\ResultField\ExportFormatResultFields',
            'PluginExportFormatTutorial\Generator\ExportFormatGenerator',
            '',
            true,
            true,
            'item'
        );
    }
}

Code for the ResultField

PluginExportFormatTutorial/src/ResultField/ExportFormatResultFields.php
<?php

namespace PluginExportFormatTutorial\ResultField;

use Plenty\Modules\Cloud\ElasticSearch\Lib\Source\Mutator\BuiltIn\LanguageMutator;
use Plenty\Modules\DataExchange\Contracts\ResultFields;
use Plenty\Modules\Helper\Services\ArrayHelper;
use Plenty\Modules\Item\Search\Mutators\BarcodeMutator;
use Plenty\Modules\Item\Search\Mutators\ImageMutator;
use Plenty\Modules\Item\Search\Mutators\KeyMutator;
use Plenty\Modules\Helper\Models\KeyValue;

/**
 * Class ExportFormatResultFields
 * @package PluginExportFormatTutorial\ResultField
 */
class ExportFormatResultFields extends ResultFields
{
    const DEFAULT_MARKET_REFERENCE = 100.00;

    /**
     * @var ArrayHelper
     */
    private $arrayHelper;

    /**
     * ExportFormatResultFields constructor.
     * @param ArrayHelper $arrayHelper
     */
    public function __construct(ArrayHelper $arrayHelper)
    {
        $this->arrayHelper = $arrayHelper;
    }

    /**
     * Creates the fields set to be retrieved from ElasticSearch.
     *
     * @param array $formatSettings
     * @return array
     */
    public function generateResultFields(array $formatSettings = []):array
    {
        /** @var KeyValue $settings */
        $settings = $this->arrayHelper->buildMapFromObjectList($formatSettings, 'key', 'value');
        $reference = $settings->get('referrerId') ? $settings->get('referrerId') : self::DEFAULT_MARKET_REFERENCE;

        $this->setOrderByList(['variation.itemId', 'ASC']);

        $itemDescriptionFields = ['texts.urlPath'];
        $itemDescriptionFields[] = ($settings->get('nameId')) ? 'texts.name' . $settings->get('nameId') : 'texts.name1';

        if($settings->get('descriptionType') == 'itemShortDescription' || $settings->get('previewTextType') == 'itemShortDescription')
        {
            $itemDescriptionFields[] = 'texts.shortDescription';
        }

        if($settings->get('descriptionType') == 'itemDescription'
            || $settings->get('descriptionType') == 'itemDescriptionAndTechnicalData'
            || $settings->get('previewTextType') == 'itemDescription'
            || $settings->get('previewTextType') == 'itemDescriptionAndTechnicalData')
        {
            $itemDescriptionFields[] = 'texts.description';
        }

        if($settings->get('descriptionType') == 'technicalData'
            || $settings->get('descriptionType') == 'itemDescriptionAndTechnicalData'
            || $settings->get('previewTextType') == 'technicalData'
            || $settings->get('previewTextType') == 'itemDescriptionAndTechnicalData')
        {
            $itemDescriptionFields[] = 'texts.technicalData';
        }

        $itemDescriptionFields[] = 'texts.lang';

        // Mutators

        /** @var ImageMutator $imageMutator */
        $imageMutator = pluginApp(ImageMutator::class);
        if($imageMutator instanceof ImageMutator)
        {
            $imageMutator->addMarket($reference);
        }

        /** @var LanguageMutator $languageMutator */
        $languageMutator = pluginApp(LanguageMutator::class, [[$settings->get('lang')]]);

        /** @var BarcodeMutator $barcodeMutator */
        $barcodeMutator = pluginApp(BarcodeMutator::class);
        if($barcodeMutator instanceof BarcodeMutator)
        {
            $barcodeMutator->addMarket($reference);
        }

        /** @var KeyMutator */
        $keyMutator = pluginApp(KeyMutator::class);
        if($keyMutator instanceof KeyMutator)
        {
            $keyMutator->setKeyList($this->getKeyList());
            $keyMutator->setNestedKeyList($this->getNestedKeyList());
        }

        // Fields
        $fields = [
            [
                //item
                'item.id',
                'item.manufacturer.id',

                //variation
                'id',
                'variation.availability.id',
                'variation.stockLimitation',
                'variation.vatId',
                'variation.model',
                'variation.isMain',
                'variation.number',

                //images
                'images.all.urlMiddle',
                'images.all.urlPreview',
                'images.all.urlSecondPreview',
                'images.all.url',
                'images.all.path',
                'images.all.position',

                'images.item.urlMiddle',
                'images.item.urlPreview',
                'images.item.urlSecondPreview',
                'images.item.url',
                'images.item.path',
                'images.item.position',

                'images.variation.urlMiddle',
                'images.variation.urlPreview',
                'images.variation.urlSecondPreview',
                'images.variation.url',
                'images.variation.path',
                'images.variation.position',

                //unit
                'unit.content',
                'unit.id',

                //defaultCategories
                'defaultCategories.id',

                //allCategories
                'ids.categories.all',

                //barcodes
                'barcodes.code',
                'barcodes.type',

                //attributes
                'attributes.attributeValueSetId',
                'attributes.attributeId',
                'attributes.valueId',

                //properties
                'properties.property.id',
                'properties.property.valueType',
                'properties.selection.name',
                'properties.selection.lang',
                'properties.texts.value',
                'properties.texts.lang'
            ],
            [
                $languageMutator,
                $barcodeMutator,
                $keyMutator
            ],
        ];

        // Get the associated images if reference is selected
        if($reference != -1)
        {
            $fields[1][] = $imageMutator;
        }

        foreach($itemDescriptionFields as $itemDescriptionField)
        {
            //texts
            $fields[0][] = $itemDescriptionField;
        }

        return $fields;
    }

    /**
     * Returns predefined keys to make sure that they will be available in the feed.
     *
     * @return array
     */
    private function getKeyList()
    {
        return [
            // Item
            'item.id',
            'item.manufacturer.id',
            'item.conditionApi',

            // Variation
            'variation.availability.id',
            'variation.model',
            'variation.releasedAt',
            'variation.stockLimitation',
            'variation.weightG',
            'variation.number',

            // Unit
            'unit.content',
            'unit.id',

            'ids.categories.all',
        ];
    }

    /**
     * Returns the predefined nested keys to make sure that they will be available in the feed.
     *
     * @return array
     */
    private function getNestedKeyList()
    {
        return [
            'keys' => [
                // Attributes
                'attributes',

                // Barcodes
                'barcodes',

                // Default categories
                'defaultCategories',

                // Images
                'images.all',
                'images.item',
                'images.variation',
            ],

            'nestedKeys' => [
                // Attributes
                'attributes' => [
                    'attributeValueSetId',
                    'attributeId',
                    'valueId'
                ],

                // Barcodes
                'barcodes' => [
                    'code',
                    'type'
                ],

                // Default categories
                'defaultCategories' => [
                    'id'
                ],

                // Images
                'images.all' => [
                    'urlMiddle',
                    'urlPreview',
                    'urlSecondPreview',
                    'url',
                    'path',
                    'position',
                ],
                'images.item' => [
                    'urlMiddle',
                    'urlPreview',
                    'urlSecondPreview',
                    'url',
                    'path',
                    'position',
                ],
                'images.variation' => [
                    'urlMiddle',
                    'urlPreview',
                    'urlSecondPreview',
                    'url',
                    'path',
                    'position',
                ],

                // texts
                'texts' => [
                    'urlPath',
                    'name1',
                    'name2',
                    'name3',
                    'shortDescription',
                    'description',
                    'technicalData',
                    'lang'
                ],
            ]
        ];
    }
}

Code for the Generator

PluginExportFormatTutorial/src/Generator/ExportFormatGenerator.php
<?php

namespace PluginExportFormatTutorial\Generator;

use ElasticExport\Helper\ElasticExportCoreHelper;
use ElasticExport\Helper\ElasticExportPriceHelper;
use ElasticExport\Helper\ElasticExportStockHelper;
use Plenty\Modules\DataExchange\Contracts\CSVPluginGenerator;
use Plenty\Modules\Helper\Services\ArrayHelper;
use Plenty\Modules\Helper\Models\KeyValue;
use Plenty\Modules\Item\Search\Contracts\VariationElasticSearchScrollRepositoryContract;
use Plenty\Plugin\Log\Loggable;

/**
 * Class ExportFormatGenerator
 * @package PluginExportFormatTutorial\Generator
 */
class ExportFormatGenerator extends CSVPluginGenerator
{
    use Loggable;

    /**
     * @var ElasticExportCoreHelper $elasticExportCoreHelper
     */
    private $elasticExportCoreHelper;

    /**
     * @var ElasticExportPriceHelper $elasticExportPriceHelper
     */
    private $elasticExportPriceHelper;

    /**
     * @var ElasticExportStockHelper $elasticExportStockHelper
     */
    private $elasticExportStockHelper;

    /**
     * @var ArrayHelper $arrayHelper
     */
    private $arrayHelper;

    /**
     * ExportFormatGenerator constructor.
     * @param ArrayHelper $arrayHelper
     */
    public function __construct(ArrayHelper $arrayHelper)
    {
        $this->arrayHelper = $arrayHelper;
    }

    /**
     * Generates and populates the data into the CSV file.
     *
     * @param VariationElasticSearchScrollRepositoryContract $elasticSearch
     * @param array $formatSettings
     * @param array $filter
     */
    protected function generatePluginContent($elasticSearch, array $formatSettings = [], array $filter = [])
    {
        $this->elasticExportCoreHelper = pluginApp(ElasticExportCoreHelper::class);
        $this->elasticExportPriceHelper = pluginApp(ElasticExportPriceHelper::class);
        $this->elasticExportStockHelper = pluginApp(ElasticExportStockHelper::class);

        /** @var KeyValue $settings */
        $settings = $this->arrayHelper->buildMapFromObjectList($formatSettings, 'key', 'value');

        $this->setDelimiter(";");

        // add header
        $this->addCSVContent([
            'VariationID',
            'VariationNo',
            'Model',
            'Name',
            'Description',
            'Image',
            'Brand',
            'Barcode',
            'Currency',
            'ShippingCosts',
            'RRP',
            'Price',
            'BasePrice',
            'BasePriceUnit',
            'Link'
        ]);

        if($elasticSearch instanceof VariationElasticSearchScrollRepositoryContract)
        {
            $limitReached = false;
            $lines = 0;

            do
            {
                if($limitReached === true)
                {
                    break;
                }

                $resultList = $elasticSearch->execute();

                foreach($resultList['documents'] as $variation)
                {
                    if($lines == $filter['limit'])
                    {
                        $limitReached = true;
                        break;
                    }

                    if(is_array($resultList['documents']) && count($resultList['documents']) > 0)
                    {
                        // filter manually for stock limitations
                        if($this->elasticExportStockHelper->isFilteredByStock($variation, $filter) === true)
                        {
                            continue;
                        }

                        try
                        {
                            $this->buildRow($variation, $settings);
                        }
                        catch(\Throwable $exception)
                        {
                            $this->getLogger('PluginExportFormatTutorial')->logException($exception);
                        }

                        $lines++;
                    }
                }
            } while ($elasticSearch->hasNext());
        }
    }

    /**
     * Builds one data row.
     *
     * @param array $variation
     * @param KeyValue $settings
     */
    private function buildRow($variation, $settings)
    {
        $priceList = $this->elasticExportPriceHelper->getPriceList($variation, $settings, 2, '.');

        if((float)$priceList['recommendedRetailPrice'] > 0)
        {
            $price = $priceList['recommendedRetailPrice'] > $priceList['price'] ? $priceList['price'] : $priceList['recommendedRetailPrice'];
        }
        else
        {
            $price = $priceList['price'];
        }

        $rrp = $priceList['recommendedRetailPrice'] > $priceList['price'] ? $priceList['recommendedRetailPrice'] : $priceList['price'];

        if((float)$rrp == 0 || (float)$price == 0 || (float)$rrp == (float)$price)
        {
            $rrp = '';
        }

        $basePriceList = $this->elasticExportPriceHelper->getBasePriceDetails($variation, (float) $priceList['price'], $settings->get('lang'));
        $deliveryCost = $this->elasticExportCoreHelper->getShippingCost($variation['data']['item']['id'], $settings);

        if(!is_null($deliveryCost))
        {
            $deliveryCost = number_format((float)$deliveryCost, 2, '.', '');
        }
        else
        {
            $deliveryCost = '';
        }

        $data = [
            'VariationID' => $variation['id'],
            'VariationNo' => $variation['data']['variation']['number'],
            'Model' => $variation['data']['variation']['model'],
            'Name' => $this->elasticExportCoreHelper->getMutatedName($variation, $settings, 256),
            'Description' => $this->elasticExportCoreHelper->getMutatedDescription($variation, $settings, 256),
            'Image' => $this->elasticExportCoreHelper->getImageListInOrder($variation, $settings, 1, ElasticExportCoreHelper::ALL_IMAGES),
            'Brand' => $this->elasticExportCoreHelper->getExternalManufacturerName((int)$variation['data']['item']['manufacturer']['id']),
            'Barcode' => $this->elasticExportCoreHelper->getBarcodeByType($variation, $settings->get('barcode')),
            'Currency' => $priceList['currency'],
            'ShippingCosts' => $deliveryCost,
            'RRP' => $rrp,
            'Price' => $price,
            'BasePrice' => $this->elasticExportPriceHelper->getBasePrice($variation, $priceList['price'], $settings->get('lang'), '/', false, false, $priceList['currency']),
            'BasePriceUnit' => $basePriceList['lot'],
            'Link' => $this->elasticExportCoreHelper->getMutatedUrl($variation, $settings),
        ];

        $this->addCSVContent(array_values($data));
    }
}

Deploying the plugin

Finally, deploy the plugin in a plugin set. The new export format will be available in the elastic export menu in plentymarkets.

  1. Go to Plugins » Plugin overview.
  2. Select the desired plugin set.
  3. Activate the plugin in the Active column.
  4. In the toolbar, click on Save & publish plugins.
  5. → Once a success message is displayed, you are ready to check the output.

Now you can export data using your newly added plugin code.