<?php

namespace PlentyConnector\Components\Bundle\PlentymarketsAdapter\QueryHandler;

use PlentyConnector\Components\Bundle\PlentymarketsAdapter\ResponseParser\BundleResponseParserInterface;
use PlentyConnector\Components\Bundle\TransferObject\Bundle;
use PlentymarketsAdapter\PlentymarketsAdapter;
use PlentymarketsAdapter\ReadApi\Item;
use PlentymarketsAdapter\ReadApi\Item\Variation;
use SystemConnector\IdentityService\IdentityServiceInterface;
use SystemConnector\ServiceBus\Query\FetchTransferObjectQuery;
use SystemConnector\ServiceBus\Query\QueryInterface;
use SystemConnector\ServiceBus\QueryHandler\QueryHandlerInterface;
use SystemConnector\ServiceBus\QueryType;

class FetchBundleQueryHandler implements QueryHandlerInterface
{
    /**
     * @var Item
     */
    private $itemApi;

    /**
     * @var Variation
     */
    private $variationApi;

    /**
     * @var IdentityServiceInterface
     */
    private $identityService;

    /**
     * @var BundleResponseParserInterface
     */
    private $responseParser;

    public function __construct(
        Item $itemApi,
        Variation $variationApi,
        IdentityServiceInterface $identityService,
        BundleResponseParserInterface $responseParser
    ) {
        $this->itemApi = $itemApi;
        $this->variationApi = $variationApi;
        $this->identityService = $identityService;
        $this->responseParser = $responseParser;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(QueryInterface $query)
    {
        return $query instanceof FetchTransferObjectQuery &&
            $query->getAdapterName() === PlentymarketsAdapter::NAME &&
            $query->getObjectType() === Bundle::TYPE &&
            $query->getQueryType() === QueryType::ALL;
    }

    /**
     * {@inheritdoc}
     *
     * @param FetchTransferObjectQuery $query
     */
    public function handle(QueryInterface $query)
    {
        $identity = $this->identityService->findOneBy([
            'objectIdentifier' => $query->getObjectIdentifier(),
            'objectType' => Bundle::TYPE,
            'adapterName' => PlentymarketsAdapter::NAME,
        ]);

        if (null === $identity) {
            return [];
        }

        $variation = $this->variationApi->findBy(['id' => $identity->getAdapterIdentifier()]);

        if (empty($variation)) {
            return [];
        }

        $variation = array_shift($variation);

        $product = $this->itemApi->findOne($variation['itemId']);

        $result = $this->responseParser->parse($product);

        return array_filter($result);
    }
}


<?php

namespace PlentymarketsAdapter\ServiceBus\CommandHandler\Order;

use PlentymarketsAdapter\Client\ClientInterface;
use PlentymarketsAdapter\PlentymarketsAdapter;
use PlentymarketsAdapter\RequestGenerator\Order\OrderRequestGeneratorInterface;
use RuntimeException;
use SystemConnector\IdentityService\Exception\NotFoundException;
use SystemConnector\IdentityService\IdentityServiceInterface;
use SystemConnector\ServiceBus\Command\CommandInterface;
use SystemConnector\ServiceBus\Command\TransferObjectCommand;
use SystemConnector\ServiceBus\CommandHandler\CommandHandlerInterface;
use SystemConnector\ServiceBus\CommandType;
use SystemConnector\TransferObject\Order\Comment\Comment;
use SystemConnector\TransferObject\Order\Order;
use SystemConnector\TransferObject\Shop\Shop;

class HandleOrderCommandHandler implements CommandHandlerInterface
{
    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @var IdentityServiceInterface
     */
    private $identityService;

    /**
     * @var OrderRequestGeneratorInterface
     */
    private $orderRequestGenerator;

    public function __construct(
        ClientInterface $client,
        IdentityServiceInterface $identityService,
        OrderRequestGeneratorInterface $orderRequestGeneretor
    ) {
        $this->client = $client;
        $this->identityService = $identityService;
        $this->orderRequestGenerator = $orderRequestGeneretor;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(CommandInterface $command)
    {
        return $command instanceof TransferObjectCommand &&
            $command->getAdapterName() === PlentymarketsAdapter::NAME &&
            $command->getObjectType() === Order::TYPE &&
            $command->getCommandType() === CommandType::HANDLE;
    }

    /**
     * {@inheritdoc}
     *
     * @var TransferObjectCommand $command
     */
    public function handle(CommandInterface $command)
    {
        /**
         * @var Order $order
         */
        $order = $command->getPayload();

        $identity = $this->identityService->findOneBy([
            'objectIdentifier' => $order->getIdentifier(),
            'objectType' => Order::TYPE,
            'adapterName' => PlentymarketsAdapter::NAME,
        ]);

        if ($identity !== null) {
            return true;
        }

        if ($this->isExistingOrder($order)) {
            return true;
        }

        $result = $this->handleOrder($order);

        if ($result) {
            $this->handleComments($order);
        }

        return true;
    }

    /**
     * @param Order $order
     *
     * @return bool
     */
    private function handleOrder(Order $order)
    {
        $params = $this->orderRequestGenerator->generate($order);
        $result = $this->client->request('post', 'orders', $params);

        $this->identityService->insert(
            $order->getIdentifier(),
            Order::TYPE,
            (string) $result['id'],
            PlentymarketsAdapter::NAME
        );

        return true;
    }

    /**
     * @param Order $order
     *
     * @return bool
     */
    private function isExistingOrder(Order $order)
    {
        $shopIdentity = $this->identityService->findOneBy([
            'objectIdentifier' => $order->getShopIdentifier(),
            'objectType' => Shop::TYPE,
            'adapterName' => PlentymarketsAdapter::NAME,
        ]);

        if (null === $shopIdentity) {
            throw new NotFoundException('shop not mapped');
        }

        $result = $this->client->request('GET', 'orders', [
            'externalOrderId' => $order->getOrderNumber(),
        ]);

        $result = array_filter($result, function (array $order) use ($shopIdentity) {
            return (int) $order['plentyId'] === $shopIdentity->getAdapterIdentifier();
        });

        if (!empty($result)) {
            return true;
        }

        return false;
    }

    /**
     * @param Order $order
     */
    private function handleComments(Order $order)
    {
        $orderIdentity = $this->identityService->findOneBy([
            'objectIdentifier' => $order->getIdentifier(),
            'objectType' => Order::TYPE,
            'adapterName' => PlentymarketsAdapter::NAME,
        ]);

        if (null === $orderIdentity) {
            throw new NotFoundException('could not find order for comment handling - ' . $order->getIdentifier());
        }

        foreach ($order->getComments() as $comment) {
            $commentParams = [
                'referenceType' => 'order',
                'referenceValue' => $orderIdentity->getAdapterIdentifier(),
                'text' => $comment->getComment(),
                'isVisibleForContact' => $comment->getType() === Comment::TYPE_CUSTOMER,
            ];

            if ($comment->getType() === Comment::TYPE_INTERNAL) {
                $commentParams['userId'] = $this->getUserId();
            }

            $this->client->request('post', 'comments', $commentParams);
        }
    }

    /**
     * @return int
     */
    private function getUserId()
    {
        static $user = null;

        if (null === $user) {
            $user = $this->client->request('GET', 'user');

            if (empty($user)) {
                throw new RuntimeException('could not read user data');
            }
        }

        return (int) $user['id'];
    }
}


<?php

namespace SystemConnector\Console\Command;

use Exception;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use SystemConnector\BacklogService\Middleware\BacklogCommandHandlerMiddleware;
use SystemConnector\ConnectorInterface;
use SystemConnector\Console\OutputHandler\OutputHandlerInterface;
use SystemConnector\Logger\ConsoleHandler;
use SystemConnector\ServiceBus\QueryType;
use Throwable;

class ProcessCommand extends Command
{
    /**
     * @var ConnectorInterface
     */
    private $connector;

    /**
     * @var OutputHandlerInterface
     */
    private $outputHandler;

    /**
     * @var LoggerInterface
     */
    private $logger;

    public function __construct(
        ConnectorInterface $connector,
        OutputHandlerInterface $outputHandler,
        LoggerInterface $logger
    ) {
        $this->connector = $connector;
        $this->outputHandler = $outputHandler;
        $this->logger = $logger;

        parent::__construct();
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->setName('plentyconnector:process');
        $this->setDescription('process definitons');
        $this->setHelp($this->getHelpText());
        $this->addArgument(
            'objectType',
            InputArgument::OPTIONAL,
            'Object type to process. Leave empty for every object type'
        );
        $this->addArgument(
            'objectIdentifier',
            InputArgument::OPTIONAL,
            'Object Identifier to process. Leave empty for every object type'
        );
        $this->addOption(
            'all',
            null,
            InputOption::VALUE_NONE,
            'If set, ignore changes and process everything'
        );
        $this->addOption(
            'disableBacklog',
            null,
            InputOption::VALUE_NONE,
            'If set, commands will be handles directly'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $all = (bool) $input->getOption('all');

        if ((bool) $input->getOption('disableBacklog')) {
            BacklogCommandHandlerMiddleware::$active = false;
        }

        $objectType = $input->getArgument('objectType');
        $objectIdentifier = $input->getArgument('objectIdentifier');

        if (method_exists($this->logger, 'pushHandler')) {
            $this->logger->pushHandler(new ConsoleHandler($output));
        }

        $this->outputHandler->initialize($input, $output);

        try {
            if ($objectIdentifier) {
                $queryType = QueryType::ONE;

                BacklogCommandHandlerMiddleware::$active = false;
            } else {
                $queryType = $all ? QueryType::ALL : QueryType::CHANGED;
            }

            $this->connector->handle($queryType, $objectType, $objectIdentifier);
        } catch (Exception $exception) {
            $this->logger->error($exception->getMessage());
        } catch (Throwable $exception) {
            $this->logger->error($exception->getMessage());
        }
    }

    /**
     * @return string
     */
    private function getHelpText()
    {
        $examples = [
            'import all products: plentyconnector:process Product --all',
            'import changed products: plentyconnector:process Product',
            'import single product: plentyconnector:process Product 753c7d5d-09be-4dd3-bd3f-3d5cc2e92dab',
            'import changed orders: plentyconnector:process Order',
        ];

        return "Examples:\n\n" . implode("\n", $examples);
    }
}


<?php

namespace SystemConnector\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use SystemConnector\BacklogService\BacklogService;
use SystemConnector\Console\OutputHandler\OutputHandlerInterface;

class BacklogInfoCommand extends Command
{
    /**
     * @var BacklogService
     */
    private $backlogService;

    /**
     * @var OutputHandlerInterface
     */
    private $outputHandler;

    public function __construct(
        BacklogService $backlogService,
        OutputHandlerInterface $outputHandler
    ) {
        $this->backlogService = $backlogService;
        $this->outputHandler = $outputHandler;

        parent::__construct();
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->setName('plentyconnector:backlog:info');
        $this->setDescription('displays information about the backlog');
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->outputHandler->initialize($input, $output);

        $info = $this->backlogService->getInfo();

        $this->outputHandler->createTable(['info', 'value'], [
            ['amount enqueued', $info['amount_enqueued']],
        ]);
    }
}


<?php

namespace SystemConnector\Console\Command;

use Exception;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use SystemConnector\BacklogService\BacklogService;
use SystemConnector\Console\OutputHandler\OutputHandlerInterface;
use SystemConnector\Logger\ConsoleHandler;
use SystemConnector\ServiceBus\ServiceBusInterface;
use Throwable;

class BacklogProcessCommand extends Command
{
    /**
     * @var ServiceBusInterface
     */
    private $serviceBus;

    /**
     * @var BacklogService
     */
    private $backlogService;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var OutputHandlerInterface
     */
    private $outputHandler;

    public function __construct(
        ServiceBusInterface $serviceBus,
        BacklogService $backlogService,
        LoggerInterface $logger,
        OutputHandlerInterface $outputHandler
    ) {
        $this->serviceBus = $serviceBus;
        $this->backlogService = $backlogService;
        $this->logger = $logger;
        $this->outputHandler = $outputHandler;

        parent::__construct();
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->setName('plentyconnector:backlog:process');
        $this->setDescription('process command backlog');
        $this->addArgument(
            'amount',
            InputArgument::OPTIONAL,
            'Amount of backlog elements to be processed',
            200
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (method_exists($this->logger, 'pushHandler')) {
            $this->logger->pushHandler(new ConsoleHandler($output));
        }

        $amount = (int) $input->getArgument('amount');

        $this->outputHandler->initialize($input, $output);
        $this->outputHandler->startProgressBar($amount);

        try {
            $counter = 0;

            while ($counter < $amount && $command = $this->backlogService->dequeue()) {
                ++$counter;

                $this->serviceBus->handle($command);
                $this->outputHandler->advanceProgressBar();
            }
        } catch (Exception $exception) {
            $this->logger->error($exception->getMessage());
        } catch (Throwable $exception) {
            $this->logger->error($exception->getMessage());
        }

        $this->outputHandler->finishProgressBar();
    }
}


<?php

namespace PlentymarketsAdapter\ServiceBus\QueryHandler\Stock;

use Exception;
use PlentymarketsAdapter\Client\ClientInterface;
use PlentymarketsAdapter\PlentymarketsAdapter;
use PlentymarketsAdapter\ResponseParser\Product\Stock\StockResponseParserInterface;
use Psr\Log\LoggerInterface;
use SystemConnector\Console\OutputHandler\OutputHandlerInterface;
use SystemConnector\ServiceBus\Query\FetchTransferObjectQuery;
use SystemConnector\ServiceBus\Query\QueryInterface;
use SystemConnector\ServiceBus\QueryHandler\QueryHandlerInterface;
use SystemConnector\ServiceBus\QueryType;
use SystemConnector\TransferObject\Product\Stock\Stock;

class FetchAllStocksQueryHandler implements QueryHandlerInterface
{
    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @var StockResponseParserInterface
     */
    private $responseParser;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var OutputHandlerInterface
     */
    private $outputHandler;

    public function __construct(
        ClientInterface $client,
        StockResponseParserInterface $responseParser,
        LoggerInterface $logger,
        OutputHandlerInterface $outputHandler
    ) {
        $this->client = $client;
        $this->responseParser = $responseParser;
        $this->logger = $logger;
        $this->outputHandler = $outputHandler;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(QueryInterface $query)
    {
        return $query instanceof FetchTransferObjectQuery &&
            $query->getAdapterName() === PlentymarketsAdapter::NAME &&
            $query->getObjectType() === Stock::TYPE &&
            $query->getQueryType() === QueryType::ALL;
    }

    /**
     * {@inheritdoc}
     */
    public function handle(QueryInterface $query)
    {
        $elements = $this->client->getIterator('items/variations', [
            'with' => 'stock',
        ]);

        $this->outputHandler->startProgressBar(count($elements));

        foreach ($elements as $element) {
            $stock = null;

            try {
                $stock = $this->responseParser->parse($element);
            } catch (Exception $exception) {
                $this->logger->error($exception->getMessage());
            }

            if ($stock !== null) {
                yield $stock;
            }

            $this->outputHandler->advanceProgressBar();
        }

        $this->outputHandler->finishProgressBar();
    }
}


<?php

namespace PlentymarketsAdapter\Helper;

use PlentymarketsAdapter\PlentymarketsAdapter;
use SystemConnector\IdentityService\IdentityServiceInterface;
use SystemConnector\TransferObject\Language\Language;

class LanguageHelper implements LanguageHelperInterface
{
    /**
     * @var IdentityServiceInterface
     */
    private $identityService;

    /**
     * @param IdentityServiceInterface $identityService
     */
    public function __construct(
        IdentityServiceInterface $identityService
    ) {
        $this->identityService = $identityService;
    }

    /**
     * {@inheritdoc}
     */
    public function getLanguages()
    {
        return [
            [
                'id' => 'bg',
                'name' => 'Bulgarian',
            ],
            [
                'id' => 'cn',
                'name' => 'Chinese',
            ],
            [
                'id' => 'cz',
                'name' => 'Czech',
            ],
            [
                'id' => 'da',
                'name' => 'Danish',
            ],
            [
                'id' => 'de',
                'name' => 'German',
            ],
            [
                'id' => 'en',
                'name' => 'English',
            ],
            [
                'id' => 'es',
                'name' => 'Spanish',
            ],
            [
                'id' => 'fr',
                'name' => 'French',
            ],
            [
                'id' => 'it',
                'name' => 'Italian',
            ],
            [
                'id' => 'nl',
                'name' => 'Dutch',
            ],
            [
                'id' => 'nn',
                'name' => 'Norwegian',
            ],
            [
                'id' => 'pl',
                'name' => 'Polish',
            ],
            [
                'id' => 'pt',
                'name' => 'Portuguese',
            ],
            [
                'id' => 'ro',
                'name' => 'Romanian',
            ],
            [
                'id' => 'ru',
                'name' => 'Russian',
            ],
            [
                'id' => 'se',
                'name' => 'Swedish',
            ],
            [
                'id' => 'sk',
                'name' => 'Slovak',
            ],
            [
                'id' => 'tr',
                'name' => 'Turkish',
            ],
            [
                'id' => 'vn',
                'name' => 'Vietnamese',
            ],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getLanguagesQueryString()
    {
        $languages = [];

        foreach ($this->getLanguages() as $language) {
            $languageIdentity = $this->identityService->findOneBy([
                'adapterIdentifier' => (string) $language['id'],
                'adapterName' => PlentymarketsAdapter::NAME,
                'objectType' => Language::TYPE,
            ]);

            if ($languageIdentity === null) {
                continue;
            }

            $isMapped = $this->identityService->isMappedIdentity(
                $languageIdentity->getObjectIdentifier(),
                Language::TYPE,
                PlentymarketsAdapter::NAME
            );

            if ($isMapped) {
                $languages[] = $language['id'];
            }
        }

        return implode(',', $languages);
    }
}


<?php

namespace ShopwareAdapter\DataPersister\Translation;

use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Shopware\Models\Article\Image as ArticleImage;
use Shopware_Components_Translation;
use ShopwareAdapter\DataProvider\Translation\TranslationDataProviderInterface;
use ShopwareAdapter\ShopwareAdapter;
use SystemConnector\IdentityService\IdentityServiceInterface;
use SystemConnector\IdentityService\Struct\Identity;
use SystemConnector\TransferObject\Category\Category;
use SystemConnector\TransferObject\Language\Language;
use SystemConnector\TransferObject\Media\Media;
use SystemConnector\TransferObject\Product\Image\Image;
use SystemConnector\TransferObject\Product\Product;
use SystemConnector\TransferObject\Product\Property\Property;
use SystemConnector\TransferObject\Product\Property\Value\Value;
use SystemConnector\Translation\TranslationHelperInterface;
use SystemConnector\ValueObject\Attribute\Attribute;

class TranslationDataPersister implements TranslationDataPersisterInterface
{
    /**
     * @var IdentityServiceInterface
     */
    private $identityService;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var TranslationDataProviderInterface
     */
    private $dataProvider;

    /**
     * @var TranslationHelperInterface
     */
    private $translationHelper;

    /**
     * @var Shopware_Components_Translation
     */
    private $shopwareTranslationManager;

    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    public function __construct(
        IdentityServiceInterface $identityService,
        LoggerInterface $logger,
        TranslationDataProviderInterface $dataProvider,
        TranslationHelperInterface $translationHelper,
        Shopware_Components_Translation $shopwareTranslationManager,
        EntityManagerInterface $entityManager
    ) {
        $this->identityService = $identityService;
        $this->logger = $logger;
        $this->dataProvider = $dataProvider;
        $this->translationHelper = $translationHelper;
        $this->shopwareTranslationManager = $shopwareTranslationManager;
        $this->entityManager = $entityManager;
    }

    /**
     * {@inheritdoc}
     */
    public function writeProductTranslations(Product $product)
    {
        $productIdentity = $this->identityService->findOneBy([
            'objectIdentifier' => $product->getIdentifier(),
            'objectType' => Product::TYPE,
            'adapterName' => ShopwareAdapter::NAME,
        ]);

        if (null === $productIdentity) {
            return;
        }

        foreach ($this->translationHelper->getLanguageIdentifiers($product) as $languageIdentifier) {
            /**
             * @var Product $translatedProduct
             */
            $translatedProduct = $this->translationHelper->translate($languageIdentifier, $product);

            $languageIdentity = $this->identityService->findOneBy([
                'objectIdentifier' => $languageIdentifier,
                'objectType' => Language::TYPE,
                'adapterName' => ShopwareAdapter::NAME,
            ]);

            if (null === $languageIdentity) {
                $this->logger->notice('language not mapped - ' . $languageIdentifier);

                continue;
            }

            $translation = [
                'name' => $translatedProduct->getName(),
                'description' => $translatedProduct->getDescription(),
                'descriptionLong' => $translatedProduct->getLongDescription(),
                'metaTitle' => $translatedProduct->getMetaTitle(),
                'metaDescription' => $translatedProduct->getMetaDescription(),
                'keywords' => $translatedProduct->getMetaKeywords(),
            ];

            foreach ($product->getAttributes() as $attribute) {
                /**
                 * @var Attribute $translatedAttribute
                 */
                $translatedAttribute = $this->translationHelper->translate($languageIdentifier, $attribute);

                $key = '__attribute_plenty_connector' . ucfirst($attribute->getKey());
                $attribute_key = strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($key)));
                $translation[$attribute_key] = $translatedAttribute->getValue();
            }

            $this->writeTranslations(
                'article',
                (int) $productIdentity->getAdapterIdentifier(),
                $translation,
                $languageIdentity
            );
        }

        foreach ($product->getProperties() as $property) {
            $this->writeGroupTranslations($property, 'propertyoption');

            foreach ($property->getValues() as $value) {
                $this->writeValueTranslations($value, 'propertyvalue');
            }
        }

        foreach ($product->getVariantConfiguration() as $variantConfiguration) {
            $this->writeGroupTranslations($variantConfiguration, 'configuratorgroup');

            foreach ($variantConfiguration->getValues() as $value) {
                $this->writeValueTranslations($value, 'configuratoroption');
            }
        }

        foreach ($product->getImages() as $articleImage) {
            $this->writeMediaTranslations($articleImage, $productIdentity->getAdapterIdentifier());
        }
    }

    /**
     * @param Category $category
     */
    public function writeCategoryTranslations(Category $category)
    {
        $categoryIdentity = $this->identityService->findOneBy([
            'objectIdentifier' => $category->getIdentifier(),
            'objectType' => Category::TYPE,
            'adapterName' => ShopwareAdapter::NAME,
        ]);

        if (null === $categoryIdentity) {
            return;
        }

        foreach ($this->translationHelper->getLanguageIdentifiers($category) as $languageIdentifier) {
            /**
             * @var Category $translatedCategory
             */
            $translatedCategory = $this->translationHelper->translate($languageIdentifier, $category);

            $languageIdentity = $this->identityService->findOneBy([
                'objectIdentifier' => $languageIdentifier,
                'objectType' => Language::TYPE,
                'adapterName' => ShopwareAdapter::NAME,
            ]);

            if (null === $languageIdentity) {
                $this->logger->notice('language not mapped - ' . $languageIdentifier);

                continue;
            }

            $translation = [
                'name' => $translatedCategory->getName(),
                'metaTitle' => $translatedCategory->getMetaTitle(),
                'metaKeywords' => $translatedCategory->getMetaKeywords(),
                'metaDescription' => $translatedCategory->getMetaDescription(),
                'cmsHeadline' => $translatedCategory->getDescription(),
                'cmsText' => $translatedCategory->getLongDescription(),
            ];

            foreach ($category->getAttributes() as $attribute) {
                /**
                 * @var Attribute $translatedAttribute
                 */
                $translatedAttribute = $this->translationHelper->translate($languageIdentifier, $attribute);

                $key = '__attribute_plenty_connector' . ucfirst($attribute->getKey());
                $attribute_key = strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($key)));
                $translation[$attribute_key] = $translatedAttribute->getValue();
            }

            $this->writeTranslations(
                'category',
                (int) $categoryIdentity->getAdapterIdentifier(),
                $translation,
                $languageIdentity
            );
        }
    }

    /**
     * @param Image $image
     */
    public function removeMediaTranslation(ArticleImage $image)
    {
        $this->entityManager->getConnection()->delete(
            's_core_translations',
            ['objectkey' => $image->getId()],
            ['objecttype' => 'articleimage']
        );
    }

    /**
     * @param Property $property
     * @param string   $type
     */
    private function writeGroupTranslations(Property $property, $type)
    {
        $groupModel = null;

        if ($type === 'propertyoption') {
            $groupModel = $this->dataProvider->getPropertyOptionByName($property);
        } elseif ($type === 'configuratorgroup') {
            $groupModel = $this->dataProvider->getConfigurationGroupByName($property);
        }

        if (null === $groupModel) {
            $this->logger->notice('property option not found - ' . $property->getName());

            return;
        }

        foreach ($this->translationHelper->getLanguageIdentifiers($property) as $languageIdentifier) {
            /**
             * @var Property $translatedProperty
             */
            $translatedProperty = $this->translationHelper->translate($languageIdentifier, $property);

            $languageIdentity = $this->identityService->findOneBy([
                'objectIdentifier' => $languageIdentifier,
                'objectType' => Language::TYPE,
                'adapterName' => ShopwareAdapter::NAME,
            ]);

            if (null === $languageIdentity) {
                $this->logger->notice('language not mapped - ' . $languageIdentifier);

                continue;
            }

            if ($type === 'propertyoption') {
                $translation = [
                    'optionName' => $translatedProperty->getName(),
                ];
            } elseif ($type === 'configuratorgroup') {
                $translation = [
                    'name' => $translatedProperty->getName(),
                ];
            }

            if (empty($translation)) {
                continue;
            }

            $this->writeTranslations(
                $type,
                $groupModel->getId(),
                $translation,
                $languageIdentity
            );
        }
    }

    /**
     * @param Value  $value
     * @param string $type
     */
    private function writeValueTranslations(Value $value, $type)
    {
        $valueModel = null;

        if ($type === 'propertyvalue') {
            $valueModel = $this->dataProvider->getPropertyValueByValue($value);
        } elseif ($type === 'configuratoroption') {
            $valueModel = $this->dataProvider->getConfigurationOptionByName($value);
        }

        if (null === $valueModel) {
            $this->logger->notice('property value not found - ' . $value->getValue());

            return;
        }

        foreach ($this->translationHelper->getLanguageIdentifiers($value) as $languageIdentifier) {
            /**
             * @var Value $translatedPropertyValue
             */
            $translatedPropertyValue = $this->translationHelper->translate($languageIdentifier, $value);

            $languageIdentity = $this->identityService->findOneBy([
                'objectIdentifier' => $languageIdentifier,
                'objectType' => Language::TYPE,
                'adapterName' => ShopwareAdapter::NAME,
            ]);

            if (null === $languageIdentity) {
                $this->logger->notice('language not mapped - ' . $languageIdentifier);

                continue;
            }

            if ($type === 'propertyvalue') {
                $translation = [
                    'optionValue' => $translatedPropertyValue->getValue(),
                ];
            } elseif ($type === 'configuratoroption') {
                $translation = [
                    'name' => $translatedPropertyValue->getValue(),
                ];
            }

            if (empty($translation)) {
                continue;
            }

            $this->writeTranslations(
                $type,
                $valueModel->getId(),
                $translation,
                $languageIdentity
            );
        }
    }

    /**
     * @param Image $imageTransferObject
     * @param $articleId
     */
    private function writeMediaTranslations($imageTransferObject, $articleId)
    {
        $mediaIdentity = $this->identityService->findOneBy([
            'objectIdentifier' => $imageTransferObject->getMediaIdentifier(),
            'objectType' => Media::TYPE,
            'adapterName' => ShopwareAdapter::NAME,
        ]);

        if (null === $mediaIdentity) {
            $this->logger->notice('image not mapped - ' . $imageTransferObject->getMediaIdentifier());

            return;
        }

        $articleImage = $this->dataProvider->getArticleImage($mediaIdentity, $articleId);

        if (null === $articleImage) {
            $this->logger->notice('image not found - ' . $mediaIdentity->getObjectIdentifier());

            return;
        }

        foreach ($this->translationHelper->getLanguageIdentifiers($imageTransferObject) as $languageIdentifier) {
            /**
             * @var Image $translatedMedia
             */
            $translatedMedia = $this->translationHelper->translate($languageIdentifier, $imageTransferObject);

            $languageIdentity = $this->identityService->findOneBy([
                'objectIdentifier' => $languageIdentifier,
                'objectType' => Language::TYPE,
                'adapterName' => ShopwareAdapter::NAME,
            ]);

            if (null === $languageIdentity) {
                $this->logger->notice('language not mapped - ' . $languageIdentifier);

                continue;
            }

            $translation = ['description' => $translatedMedia->getName()];

            if (empty($translation)) {
                continue;
            }

            $this->writeTranslations(
                'articleimage',
                $articleImage->getId(),
                $translation,
                $languageIdentity
            );
        }
    }

    /**
     * @param string   $type
     * @param int      $primaryKey
     * @param array    $translation
     * @param Identity $languageIdentity
     */
    private function writeTranslations($type, $primaryKey, array $translation, Identity $languageIdentity)
    {
        $shops = $this->dataProvider->getShopsByLocaleIdentitiy($languageIdentity);

        foreach ($shops as $shop) {
            $this->shopwareTranslationManager->write(
                $shop->getId(),
                $type,
                $primaryKey,
                $translation
            );
        }
    }
}


<?php

namespace PlentyConnector\Subscriber;

use Enlight\Event\SubscriberInterface;
use Enlight_Event_EventArgs;
use Enlight_Template_Manager;

class ControllerPathSubscriber implements SubscriberInterface
{
    /**
     * @var Enlight_Template_Manager
     */
    private $template;

    /**
     * @var string
     */
    private $pluginDirectory;

    /**
     * @param Enlight_Template_Manager $template
     * @param string                   $pluginDirectory
     */
    public function __construct(Enlight_Template_Manager $template, $pluginDirectory)
    {
        $this->template = $template;
        $this->pluginDirectory = $pluginDirectory;
    }

    /**
     * {@inheritdoc9
     */
    public static function getSubscribedEvents()
    {
        return [
            'Enlight_Controller_Dispatcher_ControllerPath_Backend_PlentyConnector' => 'onControllerBackendPlentyConnector',
        ];
    }

    /**
     * @param Enlight_Event_EventArgs $args
     *
     * @return string
     */
    public function onControllerBackendPlentyConnector(Enlight_Event_EventArgs $args)
    {
        $this->template->addTemplateDir(
            $this->pluginDirectory . '/Resources/Views/'
        );

        return $this->pluginDirectory . '/Controller/Backend/PlentyConnector.php';
    }
}


<?php

namespace PlentyConnector\Installer;

use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Tools\SchemaTool;
use Shopware\Components\Model\ModelManager;
use Shopware\Components\Plugin\Context\InstallContext;
use Shopware\Components\Plugin\Context\UninstallContext;
use Shopware\Components\Plugin\Context\UpdateContext;

class DatabaseInstaller implements InstallerInterface
{
    /**
     * @var SchemaTool
     */
    private $schemaTool;

    /**
     * @var ClassMetadata[]
     */
    private $models = [];

    public function __construct(ModelManager $entityManager, array $models)
    {
        $this->schemaTool = new SchemaTool($entityManager);

        foreach ($models as $model) {
            $this->models[] = $entityManager->getClassMetadata($model);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function install(InstallContext $context)
    {
        $this->schemaTool->updateSchema($this->models, true);
    }

    /**
     * {@inheritdoc}
     */
    public function update(UpdateContext $context)
    {
        $this->schemaTool->updateSchema($this->models, true);
    }

    /**
     * {@inheritdoc}
     */
    public function uninstall(UninstallContext $context)
    {
        if (!$context->keepUserData()) {
            $this->schemaTool->dropSchema($this->models);
        }
    }
}


<?php

namespace SystemConnector\ServiceBus\CommandHandlerMiddleware\Exception;

use Exception;
use SystemConnector\ServiceBus\Command\CommandInterface;

class NotFoundException extends Exception
{
    /**
     * @param CommandInterface $command
     *
     * @return self
     */
    public static function fromCommand(CommandInterface $command)
    {
        $name = substr(strrchr(get_class($command), '\\'), 1);

        $message = 'No matching command handler found: ' . $name;

        return new self($message);
    }
}


<?php

namespace SystemConnector\ServiceBus\QueryFactory;

use Assert\Assertion;
use SystemConnector\ServiceBus\Query\FetchTransferObjectQuery;
use SystemConnector\ServiceBus\QueryFactory\Exception\MissingQueryException;
use SystemConnector\ServiceBus\QueryType;

class QueryFactory implements QueryFactoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function create($adapterName, $objectType, $queryType, $payload = null)
    {
        Assertion::string($adapterName);
        Assertion::string($objectType);
        Assertion::inArray($queryType, QueryType::getAllTypes());

        if ($queryType === QueryType::ONE) {
            Assertion::uuid($payload);
        }

        $query = null;

        switch ($queryType) {
            case QueryType::ONE:
                $query = new FetchTransferObjectQuery($adapterName, $objectType, $queryType, $payload);

                break;
            case QueryType::CHANGED:
                $query = new FetchTransferObjectQuery($adapterName, $objectType, $queryType);

                break;
            case QueryType::ALL:
                $query = new FetchTransferObjectQuery($adapterName, $objectType, $queryType);

                break;
        }

        if (null === $query) {
            throw MissingQueryException::fromObjectData($objectType, $queryType);
        }

        return $query;
    }
}


<?php

namespace SystemConnector\ValidatorService\Exception;

use SystemConnector\TransferObject\TransferObjectInterface;
use SystemConnector\ValueObject\ValueObjectInterface;

class InvalidDataException extends \Exception
{
    /**
     * @param TransferObjectInterface|ValueObjectInterface $object
     * @param string                                       $message
     * @param string                                       $propertyPath
     * @param array                                        $parents
     *
     * @return InvalidDataException
     */
    public static function fromObject($object, $message, $propertyPath, array $parents = [])
    {
        $newMessage = '';

        foreach ($parents as $parent) {
            if ($parent instanceof ValueObjectInterface) {
                $newMessage .= get_class($parent) . ' ';
            }

            if ($parent instanceof TransferObjectInterface) {
                $newMessage .= get_class($parent) . ' ObjectIdentifier: ' . $parent->getIdentifier();
            }
        }

        if ($object instanceof ValueObjectInterface) {
            $newMessage .= ' ' . $message . ' Path: ' . $propertyPath;
        }

        if ($object instanceof TransferObjectInterface) {
            $newMessage .= ' ObjectIdentifier: ' . $object->getIdentifier() . ' Message: ' . $message . ' Path: ' . $propertyPath;
        }

        $newMessage = trim($newMessage);

        return new self($newMessage);
    }
}


<?php

namespace PlentymarketsAdapter\Helper;

class MediaCategoryHelper implements MediaCategoryHelperInterface
{
    const MANUFACTURER = 1;
    const PRODUCT = 2;
    const CATEGORY = 3;

    /**
     * {@inheritdoc}
     */
    public function getCategories()
    {
        return [
            $this::MANUFACTURER => [
                'id' => $this::MANUFACTURER,
                'name' => 'Manufacturer',
            ],
            $this::PRODUCT => [
                'id' => $this::PRODUCT,
                'name' => 'Products',
            ],
            $this::CATEGORY => [
                'id' => $this::CATEGORY,
                'name' => 'Categories',
            ],
        ];
    }
}


<?php

namespace PlentymarketsAdapter\Client;

use Assert\Assertion;
use Closure;
use Exception;
use PlentymarketsAdapter\Client\Exception\InvalidCredentialsException;
use PlentymarketsAdapter\Client\Exception\InvalidResponseException;
use PlentymarketsAdapter\Client\Exception\LimitReachedException;
use PlentymarketsAdapter\Client\Exception\LoginExpiredException;
use PlentymarketsAdapter\Client\Iterator\Iterator;
use Psr\Log\LoggerInterface;
use RuntimeException;
use SystemConnector\ConfigService\ConfigServiceInterface;
use Throwable;

class Client implements ClientInterface
{
    /**
     * @var ConfigServiceInterface
     */
    private $configService;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * api access token
     *
     * @var null|string
     */
    private $accessToken;

    /**
     * retries used for the current request
     *
     * @var int
     */
    private $retries = 0;

    public function __construct(
        ConfigServiceInterface $config,
        LoggerInterface $logger
    ) {
        $this->configService = $config;
        $this->logger = $logger;
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator($path, array $criteria = [], Closure $prepareFunction = null)
    {
        return new Iterator($path, $this, $criteria, $prepareFunction);
    }

    /**
     * {@inheritdoc}
     */
    public function getTotal($path, array $criteria = [])
    {
        $options = [
            'plainResponse' => true,
        ];

        $response = $this->request('GET', $path, $criteria, null, null, $options);

        if (isset($response['totalsCount'])) {
            return (int) $response['totalsCount'];
        }

        if (isset($response['entries'])) {
            $response = $response['entries'];
        }

        return count($response);
    }

    /**
     * {@inheritdoc}
     */
    public function request($method, $path, array $params = [], $limit = null, $offset = null, array $options = [])
    {
        Assertion::nullOrInteger($limit);
        Assertion::nullOrInteger($offset);
        Assertion::isArray($options);

        if ($this->isLoginRequired($path)) {
            $this->login();
        }

        try {
            $requestUrl = $this->getUrl($path, $options);
            $response = $this->curlRequest($requestUrl, $method, $path, $params, $limit, $offset);

            if (null === $response) {
                throw InvalidResponseException::fromParams($method, $path, $options);
            }

            $this->retries = 0;

            return $this->prepareResponse($limit, $offset, $options, $response);
        } catch (Exception $exception) {
            return $this->handleRequestException($exception, $method, $path, $params, $limit, $offset);
        } catch (Throwable $exception) {
            return $this->handleRequestException($exception, $method, $path, $params, $limit, $offset);
        }
    }

    /**
     * @param string $requestUrl
     * @param string $method
     * @param string $path
     * @param array  $params
     * @param int    $limit
     * @param int    $offset
     *
     * @return array
     */
    private function curlRequest($requestUrl, $method, $path, $params, $limit, $offset)
    {
        $curl = curl_init();

        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_USERAGENT, $this->getUserAgent());
        curl_setopt($curl, CURLOPT_HTTPHEADER, $this->getHeaders($path));
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, 60);
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 60);

        $headers = [];
        curl_setopt($curl, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$headers) {
            if (stripos($header, 'X-Plenty') === false) {
                return strlen($header);
            }

            $name = substr($header, 0, strpos($header, ':'));
            $value = substr($header, strpos($header, ':') + 1);

            $headers[$name] = (int) trim($value);

            return strlen($header);
        });

        if (null !== $limit) {
            $params['itemsPerPage'] = (int) $limit;
        }

        if (null !== $offset) {
            $params['page'] = $this->getPage($limit, $offset);
        }

        $method = strtoupper($method);
        if ($method === 'POST') {
            curl_setopt($curl, CURLOPT_POST, true);
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($params, JSON_PRETTY_PRINT));
        } elseif ($method === 'GET') {
            $requestUrl = $requestUrl . '?' . http_build_query($params);
        }

        curl_setopt($curl, CURLOPT_URL, $requestUrl);

        $response = curl_exec($curl);
        $info = curl_getinfo($curl);
        $errno = curl_errno($curl);

        if ($errno !== CURLE_OK) {
            throw new RuntimeException('curl client error:' . $errno);
        }

        $this->handeRateLimits($headers);

        if ($info['http_code'] === 401) {
            throw new LoginExpiredException();
        }

        if ($info['http_code'] === 200) {
            $json = json_decode($response, true);

            if (json_last_error() !== JSON_ERROR_NONE) {
                throw InvalidResponseException::fromParams(
                    $method,
                    $path,
                    $params
                );
            }

            return $json;
        }

        throw new RuntimeException('client error');
    }

    /**
     * @param string $path
     *
     * @return bool
     */
    private function isLoginRequired($path)
    {
        if ('login' === $path) {
            return false;
        }

        if (null !== $this->accessToken) {
            return false;
        }

        return true;
    }

    private function login()
    {
        if (null === $this->configService->get('rest_username') || null === $this->configService->get('rest_password')) {
            throw new InvalidCredentialsException('invalid creddentials');
        }

        $login = $this->request('POST', 'login', [
            'username' => $this->configService->get('rest_username'),
            'password' => $this->configService->get('rest_password'),
        ]);

        if (empty($login['accessToken'])) {
            throw new RuntimeException('could not read access token from login response');
        }

        $this->accessToken = $login['accessToken'];
    }

    /**
     * @param int $limit
     * @param int $offset
     *
     * @return int
     */
    private function getPage($limit, $offset)
    {
        $page = 1.0;

        if (null !== $offset) {
            $page = (floor($offset / $limit) + 1);
        }

        return (int) $page;
    }

    /**
     * @param string $path
     * @param array  $options
     *
     * @return string
     */
    private function getUrl($path, array $options = [])
    {
        Assertion::string($path);
        Assertion::notBlank($path);

        if (!isset($options['base_uri']) && !isset($options['foreign'])) {
            $base_uri = $this->getBaseUri($this->configService->get('rest_url'));
        } elseif ($options['foreign']) {
            $base_uri = str_replace('/rest', '', $this->getBaseUri($this->configService->get('rest_url')));
        } else {
            $base_uri = $this->getBaseUri($options['base_uri']);
        }

        return $base_uri . $path;
    }

    /**
     * @param string $url
     *
     * @return string
     */
    private function getBaseUri($url)
    {
        if (null === $url) {
            throw new InvalidCredentialsException('invalid creddentials');
        }

        $parts = parse_url($url);

        if (empty($parts['scheme'])) {
            $parts['scheme'] = 'http';
        }

        return sprintf('%s://%s/%s/', $parts['scheme'], $parts['host'], 'rest');
    }

    /**
     * @param string $path
     *
     * @return array
     */
    private function getHeaders($path)
    {
        $headers = [
            'Content-Type: application/json',
            'Accept: application/x.plentymarkets.v1+json',
            'cache-control: no-cache',
            'user-agent: ' . $this->getUserAgent(),
        ];

        if ('login' !== $path) {
            $headers[] = 'Authorization: Bearer ' . $this->accessToken;
        }

        return $headers;
    }

    /**
     * @return string
     */
    private function getUserAgent()
    {
        return 'Shopware/PlentyConnector/2.0/Rest/v1';
    }

    /**
     * @param Throwable $exception
     * @param string    $method
     * @param string    $path
     * @param array     $params
     * @param int       $limit
     * @param int       $offset
     *
     * @return array
     */
    private function handleRequestException(Throwable $exception, $method, $path, array $params, $limit, $offset)
    {
        if ($this->retries >= 4) {
            $this->retries = 0;

            throw $exception;
        }

        ++$this->retries;

        if ($exception instanceof LoginExpiredException) {
            $this->accessToken = null;
        }

        if ($exception instanceof LimitReachedException) {
            if ($exception->getRetryAfter() > 60 * 15) {
                $this->logger->error('rate limit reached and retry after value is too high, aborting');
            } else {
                $this->logger->warning(
                    sprintf('rate limit reached, retrying in %s seconds', $exception->getRetryAfter())
                );

                sleep($exception->getRetryAfter());
            }
        } else {
            sleep(10);
        }

        return $this->request($method, $path, $params, $limit, $offset);
    }

    /**
     * @param int   $limit
     * @param int   $offset
     * @param array $options
     * @param array $response
     *
     * @return array
     */
    private function prepareResponse($limit, $offset, array $options, array $response)
    {
        if (!isset($options['plainResponse']) || !$options['plainResponse']) {
            // Hack to ensure that the correct page is returned from the api
            if (isset($response['page']) && $response['page'] !== $this->getPage($limit, $offset)) {
                $response['entries'] = [];
            }

            if (isset($response['entries'])) {
                $response = $response['entries'];
            }
        }

        return $response;
    }

    /**
     * @param array $headers
     */
    private function handeRateLimits(array $headers)
    {
        $limitHeaders = [
            'X-Plenty-Global-Long-Period',
            'X-Plenty-Global-Short-Period',
            'X-Plenty-Route',
        ];

        $retryAfter = 0;

        foreach ($limitHeaders as $header) {
            $callsLeftHeader = $header . '-Calls-Left';

            if (!isset($headers[$callsLeftHeader]) || $headers[$callsLeftHeader] > 0) {
                continue;
            }

            $retryAfter = $headers[$header . '-Decay'];
        }

        if ($retryAfter > 0) {
            throw new LimitReachedException($retryAfter);
        }
    }
}


<?php

namespace SystemConnector\DefinitionFactory;

use SystemConnector\DefinitionProvider\Struct\Definition;
use SystemConnector\ValidatorService\ValidatorServiceInterface;

class DefinitionFactory implements DefinitionFactoryInterface
{
    /**
     * @var ValidatorServiceInterface
     */
    private $validator;

    public function __construct(ValidatorServiceInterface $validator)
    {
        $this->validator = $validator;
    }

    /**
     * {@inheritdoc}
     */
    public function factory($originAdapterName, $destinationAdapterName, $objectType, $priority = null)
    {
        $definition = new Definition();
        $definition->setOriginAdapterName($originAdapterName);
        $definition->setDestinationAdapterName($destinationAdapterName);
        $definition->setObjectType($objectType);
        $definition->setPriority($priority);

        $this->validator->validate($definition);

        return $definition;
    }
}


<?php

namespace SystemConnector\IdentityService;

use Assert\Assertion;
use Ramsey\Uuid\Uuid;
use SystemConnector\IdentityService\Exception\NotFoundException;
use SystemConnector\IdentityService\Storage\IdentityServiceStorageInterface;
use SystemConnector\IdentityService\Struct\Identity;
use SystemConnector\ValidatorService\ValidatorServiceInterface;
use Traversable;

class IdentityService implements IdentityServiceInterface
{
    /**
     * @var IdentityServiceStorageInterface[]|Traversable
     */
    private $storages;

    /**
     * @var ValidatorServiceInterface
     */
    private $validator;

    /**
     * @param IdentityServiceStorageInterface[]|Traversable $storage
     * @param ValidatorServiceInterface                     $validator
     */
    public function __construct(
        Traversable $storage,
        ValidatorServiceInterface $validator
    ) {
        $this->storages = iterator_to_array($storage);
        $this->validator = $validator;
    }

    /**
     * {@inheritdoc}
     */
    public function findOneOrThrow($adapterIdentifier, $adapterName, $objectType)
    {
        Assertion::string($adapterIdentifier);
        Assertion::notBlank($adapterIdentifier);
        Assertion::string($adapterName);
        Assertion::notBlank($adapterName);
        Assertion::string($objectType);
        Assertion::notBlank($objectType);

        $identity = $this->findOneBy([
            'objectType' => $objectType,
            'adapterIdentifier' => $adapterIdentifier,
            'adapterName' => $adapterName,
        ]);

        if (null === $identity) {
            throw new NotFoundException(printf(
                'Could not find identity for %s with identifier %s in %s.',
                $objectType,
                $adapterIdentifier,
                $adapterName
            ));
        }

        $this->validator->validate($identity);

        return $identity;
    }

    /**
     * {@inheritdoc}
     */
    public function findOneOrCreate($adapterIdentifier, $adapterName, $objectType)
    {
        Assertion::string($adapterIdentifier);
        Assertion::notBlank($adapterIdentifier);
        Assertion::string($adapterName);
        Assertion::notBlank($adapterName);
        Assertion::string($objectType);
        Assertion::notBlank($objectType);

        $identity = $this->findOneBy([
            'objectType' => $objectType,
            'adapterIdentifier' => $adapterIdentifier,
            'adapterName' => $adapterName,
        ]);

        if (null === $identity) {
            $objectIdentifier = Uuid::uuid4()->toString();

            $identity = $this->insert(
                $objectIdentifier,
                $objectType,
                (string) $adapterIdentifier,
                $adapterName
            );
        }

        $this->validator->validate($identity);

        return $identity;
    }

    /**
     * {@inheritdoc}
     */
    public function findOneBy(array $criteria = [])
    {
        Assertion::isArray($criteria);

        $storage = reset($this->storages);
        $identity = $storage->findOneBy($criteria);

        if ($identity !== null) {
            $this->validator->validate($identity);

            return $identity;
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function insert($objectIdentifier, $objectType, $adapterIdentifier, $adapterName)
    {
        /**
         * @var Identity $identity
         */
        $identity = Identity::fromArray([
            'objectIdentifier' => $objectIdentifier,
            'objectType' => $objectType,
            'adapterIdentifier' => $adapterIdentifier,
            'adapterName' => $adapterName,
        ]);

        $this->validator->validate($identity);

        $storage = reset($this->storages);
        $storage->insert($identity);

        return $identity;
    }

    /**
     * {@inheritdoc}
     */
    public function findBy(array $criteria = [])
    {
        Assertion::isArray($criteria);

        $storage = reset($this->storages);
        $identities = $storage->findBy($criteria);

        array_walk($identities, function (Identity $identity) {
            $this->validator->validate($identity);
        });

        return $identities;
    }

    /**
     * {@inheritdoc}
     */
    public function update(Identity $identity, array $params = [])
    {
        $this->validator->validate($identity);

        $newIdentity = clone $identity;

        if (!empty($params['objectIdentifier'])) {
            $newIdentity->setObjectIdentifier($params['objectIdentifier']);
        }
        if (!empty($params['objectType'])) {
            $newIdentity->setAdapterName($params['objectType']);
        }
        if (!empty($params['adapterIdentifier'])) {
            $newIdentity->setAdapterIdentifier($params['adapterIdentifier']);
        }
        if (!empty($params['adapterName'])) {
            $newIdentity->setObjectType($params['adapterName']);
        }

        $this->validator->validate($newIdentity);

        $storage = reset($this->storages);
        $storage->update($identity, $params);

        return $newIdentity;
    }

    /**
     * {@inheritdoc}
     */
    public function remove(Identity $identity)
    {
        $this->validator->validate($identity);

        $storage = reset($this->storages);
        $storage->remove($identity);
    }

    /**
     * {@inheritdoc}
     */
    public function exists(array $criteria = [])
    {
        $identity = $this->findOneBy($criteria);

        return (bool) $identity;
    }

    /**
     * {@inheritdoc}
     */
    public function isMappedIdentity($objectIdentifier, $objectType, $adapterName)
    {
        $identities = $this->findBy([
            'objectIdentifier' => $objectIdentifier,
            'objectType' => $objectType,
        ]);

        $otherIdentities = array_filter($identities, function (Identity $identity) use ($adapterName) {
            return $identity->getAdapterName() !== $adapterName;
        });

        if (empty($otherIdentities)) {
            return false;
        }

        return true;
    }
}


<?php

namespace PlentymarketsAdapter\ResponseParser\Product\Variation;

use DateTimeImmutable;
use PlentymarketsAdapter\Helper\ReferenceAmountCalculatorInterface;
use PlentymarketsAdapter\Helper\VariationHelperInterface;
use PlentymarketsAdapter\PlentymarketsAdapter;
use PlentymarketsAdapter\ReadApi\Availability as AvailabilityApi;
use PlentymarketsAdapter\ReadApi\Item\Attribute as AttributeApi;
use PlentymarketsAdapter\ReadApi\Item\Barcode as BarcodeApi;
use PlentymarketsAdapter\ResponseParser\Product\Image\ImageResponseParserInterface;
use PlentymarketsAdapter\ResponseParser\Product\Price\PriceResponseParserInterface;
use PlentymarketsAdapter\ResponseParser\Product\Stock\StockResponseParserInterface;
use SystemConnector\ConfigService\ConfigServiceInterface;
use SystemConnector\IdentityService\Exception\NotFoundException;
use SystemConnector\IdentityService\IdentityServiceInterface;
use SystemConnector\TransferObject\Language\Language;
use SystemConnector\TransferObject\Product\Barcode\Barcode;
use SystemConnector\TransferObject\Product\Image\Image;
use SystemConnector\TransferObject\Product\Product;
use SystemConnector\TransferObject\Product\Property\Property;
use SystemConnector\TransferObject\Product\Property\Value\Value;
use SystemConnector\TransferObject\Product\Variation\Variation;
use SystemConnector\TransferObject\TransferObjectInterface;
use SystemConnector\TransferObject\Unit\Unit;
use SystemConnector\ValueObject\Translation\Translation;

class VariationResponseParser implements VariationResponseParserInterface
{
    /**
     * @var IdentityServiceInterface
     */
    private $identityService;

    /**
     * @var PriceResponseParserInterface
     */
    private $priceResponseParser;

    /**
     * @var ImageResponseParserInterface
     */
    private $imageResponseParser;

    /**
     * @var StockResponseParserInterface
     */
    private $stockResponseParser;

    /**
     * @var AvailabilityApi
     */
    private $availabilitiesApi;

    /**
     * @var AttributeApi
     */
    private $itemAttributesApi;

    /**
     * @var BarcodeApi
     */
    private $itemBarcodeApi;

    /**
     * @var ReferenceAmountCalculatorInterface
     */
    private $referenceAmountCalculator;

    /**
     * @var VariationHelperInterface
     */
    private $variationHelper;

    /**
     * @var ConfigServiceInterface
     */
    private $configService;

    public function __construct(
        IdentityServiceInterface $identityService,
        PriceResponseParserInterface $priceResponseParser,
        ImageResponseParserInterface $imageResponseParser,
        StockResponseParserInterface $stockResponseParser,
        AvailabilityApi $availabilitiesApi,
        AttributeApi $itemAttributesApi,
        BarcodeApi $itemBarcodeApi,
        ReferenceAmountCalculatorInterface $referenceAmountCalculator,
        VariationHelperInterface $variationHelper,
        ConfigServiceInterface $configService
    ) {
        $this->identityService = $identityService;
        $this->priceResponseParser = $priceResponseParser;
        $this->imageResponseParser = $imageResponseParser;
        $this->stockResponseParser = $stockResponseParser;
        $this->availabilitiesApi = $availabilitiesApi;
        $this->itemAttributesApi = $itemAttributesApi;
        $this->itemBarcodeApi = $itemBarcodeApi;
        $this->referenceAmountCalculator = $referenceAmountCalculator;
        $this->variationHelper = $variationHelper;
        $this->configService = $configService;
    }

    /**
     * @param array $product
     *
     * @return TransferObjectInterface[]
     */
    public function parse(array $product)
    {
        $productIdentitiy = $this->identityService->findOneBy([
            'adapterIdentifier' => (string) $product['id'],
            'adapterName' => PlentymarketsAdapter::NAME,
            'objectType' => Product::TYPE,
        ]);

        if (null === $productIdentitiy) {
            return [];
        }

        $variations = $product['variations'];

        $mainVariation = $this->variationHelper->getMainVariation($variations);

        if (empty($mainVariation)) {
            return [];
        }

        if (Product::MULTIPACK === $product['itemType']) {
            $variations = array_filter($variations, function (array $variation) {
                return $variation['isMain'];
            });
        }

        if (count($variations) > 1) {
            $variations = array_filter($variations, function (array $variation) {
                return !empty($variation['variationAttributeValues']);
            });
        }

        usort($variations, function (array $a, array $b) {
            if ((int) $a['position'] === (int) $b['position']) {
                return 0;
            }

            return ((int) $a['position'] < (int) $b['position']) ? -1 : 1;
        });

        $result = [];

        foreach ($variations as $variation) {
            $identity = $this->identityService->findOneOrCreate(
                (string) $variation['id'],
                PlentymarketsAdapter::NAME,
                Variation::TYPE
            );

            $variationObject = new Variation();
            $variationObject->setIdentifier($identity->getObjectIdentifier());
            $variationObject->setProductIdentifier($productIdentitiy->getObjectIdentifier());
            $variationObject->setActive((bool) $variation['isActive']);
            $variationObject->setNumber($this->getVariationNumber($variation));
            $variationObject->setStockLimitation($this->getStockLimitation($variation));
            $variationObject->setBarcodes($this->getBarcodes($variation));
            $variationObject->setPosition((int) $variation['position']);
            $variationObject->setModel((string) $variation['model']);
            $variationObject->setImages($this->getVariationImages($product['texts'], $variation, $result));
            $variationObject->setPrices($this->priceResponseParser->parse($variation));
            $variationObject->setPurchasePrice((float) $variation['purchasePrice']);
            $variationObject->setUnitIdentifier($this->getUnitIdentifier($variation));
            $variationObject->setContent((float) $variation['unit']['content']);
            $variationObject->setReferenceAmount($this->referenceAmountCalculator->calculate($variation));
            $variationObject->setMaximumOrderQuantity((float) $variation['maximumOrderQuantity']);
            $variationObject->setMinimumOrderQuantity((float) $variation['minimumOrderQuantity']);
            $variationObject->setIntervalOrderQuantity((float) $variation['intervalOrderQuantity']);
            $variationObject->setReleaseDate($this->getReleaseDate($variation));
            $variationObject->setShippingTime($this->getShippingTime($variation));
            $variationObject->setWidth((int) $variation['widthMM']);
            $variationObject->setHeight((int) $variation['heightMM']);
            $variationObject->setLength((int) $variation['lengthMM']);
            $variationObject->setWeight($this->getVariationWeight($variation));
            $variationObject->setProperties($this->getVariationProperties($variation));

            $stockObject = $this->stockResponseParser->parse($variation);

            if (null === $stockObject) {
                continue;
            }

            $importVariationsWithoutStock = json_decode($this->configService->get('import_variations_without_stock', true));

            if (!$importVariationsWithoutStock && empty($stockObject->getStock())) {
                continue;
            }

            $result[$variationObject->getIdentifier()] = $variationObject;
            $result[$stockObject->getIdentifier()] = $stockObject;
        }

        $variations = array_filter($result, function (TransferObjectInterface $object) {
            return $object instanceof Variation;
        });

        $mainVariationNumber = $this->variationHelper->getMainVariationNumber($mainVariation, $variations);

        /**
         * @var Variation $variation
         */
        foreach ($variations as &$variation) {
            if ($variation->getNumber() === $mainVariationNumber) {
                $variation->setIsMain(true);

                $checkActiveMainVariation = json_decode($this->configService->get('check_active_main_variation'));

                if ($checkActiveMainVariation && !$mainVariation['isActive']) {
                    $variation->setActive(false);
                }

                break;
            }
        }

        return $result;
    }

    /**
     * @param array $element
     *
     * @return string
     */
    private function getVariationNumber(array $element)
    {
        if ($this->configService->get('variation_number_field', 'number') === 'number') {
            return (string) $element['number'];
        }

        return (string) $element['id'];
    }

    /**
     * @param array $variation
     *
     * @return null|DateTimeImmutable
     */
    private function getReleaseDate(array $variation)
    {
        if (null !== $variation['releasedAt']) {
            return new DateTimeImmutable($variation['releasedAt']);
        }

        return null;
    }

    /**
     * @param array $texts
     * @param array $variation
     * @param array $result
     *
     * @return Image[]
     */
    private function getVariationImages(array $texts, array $variation, array &$result)
    {
        $images = [];

        foreach ((array) $variation['images'] as $entry) {
            $images[] = $this->imageResponseParser->parseImage($entry, $texts, $result);
        }

        return array_filter($images);
    }

    /**
     * @param array $variation
     *
     * @return null|string
     */
    private function getUnitIdentifier(array $variation)
    {
        if (empty($variation['unit'])) {
            return null;
        }

        // Unit
        $unitIdentity = $this->identityService->findOneBy([
            'adapterIdentifier' => (string) $variation['unit']['unitId'],
            'adapterName' => PlentymarketsAdapter::NAME,
            'objectType' => Unit::TYPE,
        ]);

        if (null === $unitIdentity) {
            throw new NotFoundException('missing mapping for unit');
        }

        return $unitIdentity->getObjectIdentifier();
    }

    /**
     * @param array $variation
     *
     * @return int
     */
    private function getShippingTime(array $variation)
    {
        static $shippingConfigurations;

        if (null === $shippingConfigurations) {
            $shippingConfigurations = $this->availabilitiesApi->findAll();
        }

        $shippingConfiguration = array_filter($shippingConfigurations, function (array $configuration) use ($variation) {
            return $configuration['id'] === $variation['availability'];
        });

        if (empty($shippingConfiguration)) {
            return 0;
        }

        $shippingConfiguration = array_shift($shippingConfiguration);

        if (empty($shippingConfiguration['averageDays'])) {
            return 0;
        }

        return (int) $shippingConfiguration['averageDays'];
    }

    /**
     * @param array $variation
     *
     * @return Barcode[]
     */
    private function getBarcodes(array $variation)
    {
        static $barcodeMapping;

        if (null === $barcodeMapping) {
            $systemBarcodes = $this->itemBarcodeApi->findAll();

            foreach ($systemBarcodes as $systemBarcode) {
                $typeMapping = [
                    'GTIN_13' => Barcode::TYPE_GTIN13,
                    'GTIN_128' => Barcode::TYPE_GTIN128,
                    'UPC' => Barcode::TYPE_UPC,
                    'ISBN' => Barcode::TYPE_ISBN,
                ];

                if (array_key_exists($systemBarcode['type'], $typeMapping)) {
                    $barcodeMapping[$systemBarcode['id']] = $typeMapping[$systemBarcode['type']];
                }
            }

            $barcodeMapping = array_filter($barcodeMapping);
        }

        $barcodes = array_filter($variation['variationBarcodes'], function (array $barcode) use ($barcodeMapping) {
            return array_key_exists($barcode['barcodeId'], $barcodeMapping);
        });

        $barcodes = array_map(function (array $barcode) use ($barcodeMapping) {
            $barcodeObject = new Barcode();
            $barcodeObject->setType($barcodeMapping[$barcode['barcodeId']]);
            $barcodeObject->setCode($barcode['code']);

            return $barcodeObject;
        }, $barcodes);

        return $barcodes;
    }

    /**
     * @param array $variation
     *
     * @return Property[]
     */
    private function getVariationProperties(array $variation)
    {
        static $attributes;

        $result = [];
        foreach ((array) $variation['variationAttributeValues'] as $attributeValue) {
            if (!isset($attributes[$attributeValue['attributeId']])) {
                $attributes[$attributeValue['attributeId']] = $this->itemAttributesApi->findOne($attributeValue['attributeId']);
            }

            $values = $attributes[$attributeValue['attributeId']]['values'];

            $attributes[$attributeValue['attributeId']]['values'] = [];

            foreach ((array) $values as $value) {
                $attributes[$attributeValue['attributeId']]['values'][$value['id']] = $value;
            }

            if (!isset($attributes[$attributeValue['attributeId']]['values'][$attributeValue['valueId']]['valueNames'])) {
                continue;
            }

            $propertyNames = $attributes[$attributeValue['attributeId']]['attributeNames'];
            $propertyPosition = $attributes[$attributeValue['attributeId']]['position'];
            $valueNames = $attributes[$attributeValue['attributeId']]['values'][$attributeValue['valueId']]['valueNames'];
            $valuePosition = $attributes[$attributeValue['attributeId']]['values'][$attributeValue['valueId']]['position'];

            $value = Value::fromArray([
                'value' => $valueNames[0]['name'],
                'position' => $valuePosition,
                'translations' => $this->getVariationPropertyValueTranslations($valueNames),
            ]);

            $result[] = Property::fromArray([
                'name' => $propertyNames[0]['name'],
                'position' => $propertyPosition,
                'values' => [$value],
                'translations' => $this->getVariationPropertyTranslations($propertyNames),
            ]);
        }

        return $result;
    }

    /**
     * @param array $names
     *
     * @return Translation[]
     */
    private function getVariationPropertyValueTranslations(array $names)
    {
        $translations = [];

        foreach ($names as $name) {
            $languageIdentifier = $this->identityService->findOneBy([
                'adapterIdentifier' => $name['lang'],
                'adapterName' => PlentymarketsAdapter::NAME,
                'objectType' => Language::TYPE,
            ]);

            if (null === $languageIdentifier) {
                continue;
            }

            $translations[] = Translation::fromArray([
                'languageIdentifier' => $languageIdentifier->getObjectIdentifier(),
                'property' => 'value',
                'value' => $name['name'],
            ]);
        }

        return $translations;
    }

    /**
     * @param array $names
     *
     * @return Translation[]
     */
    private function getVariationPropertyTranslations(array $names)
    {
        $translations = [];

        foreach ($names as $name) {
            $languageIdentifier = $this->identityService->findOneBy([
                'adapterIdentifier' => $name['lang'],
                'adapterName' => PlentymarketsAdapter::NAME,
                'objectType' => Language::TYPE,
            ]);

            if (null === $languageIdentifier) {
                continue;
            }

            $translations[] = Translation::fromArray([
                'languageIdentifier' => $languageIdentifier->getObjectIdentifier(),
                'property' => 'name',
                'value' => $name['name'],
            ]);
        }

        return $translations;
    }

    /**
     * @param array $variation
     *
     * @return float
     */
    private function getVariationWeight(array $variation)
    {
        if ($variation['weightNetG'] > 0) {
            $weight = $variation['weightNetG'];
        } else {
            $weight = $variation['weightG'];
        }

        return (float) ($weight / 1000);
    }

    /**
     * @param array $variation
     *
     * @return bool
     */
    private function getStockLimitation(array $variation)
    {
        if ($variation['stockLimitation'] == 1) {
            return true;
        }

        return false;
    }
}


<?php

namespace PlentymarketsAdapter\ServiceBus\QueryHandler\Manufacturer;

use PlentymarketsAdapter\Client\ClientInterface;
use PlentymarketsAdapter\PlentymarketsAdapter;
use PlentymarketsAdapter\ResponseParser\Manufacturer\ManufacturerResponseParserInterface;
use SystemConnector\IdentityService\IdentityServiceInterface;
use SystemConnector\ServiceBus\Query\FetchTransferObjectQuery;
use SystemConnector\ServiceBus\Query\QueryInterface;
use SystemConnector\ServiceBus\QueryHandler\QueryHandlerInterface;
use SystemConnector\ServiceBus\QueryType;
use SystemConnector\TransferObject\Manufacturer\Manufacturer;

class FetchManufacturerQueryHandler implements QueryHandlerInterface
{
    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @var ManufacturerResponseParserInterface
     */
    private $manufacturerResponseParser;

    /**
     * @var IdentityServiceInterface
     */
    private $identityService;

    public function __construct(
        ClientInterface $client,
        ManufacturerResponseParserInterface $manufacturerResponseParser,
        IdentityServiceInterface $identityService
    ) {
        $this->client = $client;
        $this->manufacturerResponseParser = $manufacturerResponseParser;
        $this->identityService = $identityService;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(QueryInterface $query)
    {
        return $query instanceof FetchTransferObjectQuery &&
            $query->getAdapterName() === PlentymarketsAdapter::NAME &&
            $query->getObjectType() === Manufacturer::TYPE &&
            $query->getQueryType() === QueryType::ONE;
    }

    /**
     * {@inheritdoc}
     *
     * @var FetchTransferObjectQuery $query
     */
    public function handle(QueryInterface $query)
    {
        $identity = $this->identityService->findOneBy([
            'objectIdentifier' => $query->getObjectIdentifier(),
            'objectType' => Manufacturer::TYPE,
            'adapterName' => PlentymarketsAdapter::NAME,
        ]);

        if (null === $identity) {
            return [];
        }

        $element = $this->client->request('GET', 'items/manufacturers/' . $identity->getAdapterIdentifier());

        $result = $this->manufacturerResponseParser->parse($element);

        return array_filter($result);
    }
}


<?php

namespace SystemConnector\ServiceBus\QueryHandlerMiddleware;

use League\Tactician\Middleware;
use SystemConnector\ServiceBus\Query\QueryInterface;
use SystemConnector\ServiceBus\QueryHandler\QueryHandlerInterface;
use SystemConnector\ServiceBus\QueryHandlerMiddleware\Exception\NotFoundException;
use Traversable;

class QueryHandlerMiddleware implements Middleware
{
    /**
     * @var QueryHandlerInterface[]|Traversable
     */
    private $handlers;

    /**
     * @param QueryHandlerInterface[]|Traversable $handlers
     */
    public function __construct(Traversable $handlers)
    {
        $this->handlers = iterator_to_array($handlers);
    }

    /**
     * @param QueryInterface $query
     * @param callable       $next
     *
     * @return mixed
     */
    public function execute($query, callable $next)
    {
        if (null === $this->handlers) {
            return $next($query);
        }

        if (!($query instanceof QueryInterface)) {
            return $next($query);
        }

        $handlers = array_filter($this->handlers, function (QueryHandlerInterface $handler) use ($query) {
            return $handler->supports($query);
        });

        if (0 === count($handlers)) {
            throw NotFoundException::fromQuery($query);
        }

        /**
         * @var QueryHandlerInterface $handler
         */
        $handler = array_shift($handlers);
        $response = $handler->handle($query);

        if (null !== $response) {
            return $response;
        }

        return $next($query);
    }
}


<?php

namespace ShopwareAdapter\ResponseParser\OrderItem;

use Doctrine\ORM\EntityRepository;
use InvalidArgumentException;
use Shopware\Models\Tax\Tax;
use ShopwareAdapter\ResponseParser\GetAttributeTrait;
use ShopwareAdapter\ShopwareAdapter;
use SystemConnector\ConfigService\ConfigServiceInterface;
use SystemConnector\IdentityService\Exception\NotFoundException;
use SystemConnector\IdentityService\IdentityServiceInterface;
use SystemConnector\TransferObject\Order\OrderItem\OrderItem;
use SystemConnector\TransferObject\VatRate\VatRate;

class OrderItemResponseParser implements OrderItemResponseParserInterface
{
    use GetAttributeTrait;

    const ITEM_TYPE_ID_VOUCHER = 2;
    const ITEM_TYPE_ID_DISCOUNT = 3;
    const ITEM_TYPE_ID_SURCHARGE = 4;

    /**
     * @var EntityRepository
     */
    private $taxRepository;

    /**
     * @var ConfigServiceInterface
     */
    private $configService;

    /**
     * @var IdentityServiceInterface
     */
    private $identityService;

    public function __construct(
        IdentityServiceInterface $identityService,
        EntityRepository $taxRepository,
        ConfigServiceInterface $configService
    ) {
        $this->identityService = $identityService;
        $this->taxRepository = $taxRepository;
        $this->configService = $configService;
    }

    /**
     * {@inheritdoc}
     */
    public function parse(array $entry, $taxFree = false)
    {
        if (empty($entry['attribute'])) {
            $entry['attribute'] = [];
        }

        /**
         * @var OrderItem $orderItem
         */
        return OrderItem::fromArray([
            'type' => $this->getItemType($entry['mode']),
            'quantity' => (float) $entry['quantity'],
            'name' => $entry['articleName'],
            'number' => $entry['articleNumber'],
            'price' => $this->getPrice($entry, $taxFree),
            'vatRateIdentifier' => $this->getVatRateIdentifier($entry),
            'attributes' => $this->getAttributes($entry['attribute']),
        ]);
    }

    /**
     * @param array $entry
     *
     * @return null|string
     */
    private function getVatRateIdentifier(array $entry)
    {
        /**
         * @var null|Tax $taxModel
         */
        $taxModel = $this->taxRepository->findOneBy(['tax' => $entry['taxRate']]);

        if (null === $taxModel) {
            throw new InvalidArgumentException('no matching tax rate found - ' . $entry['taxRate']);
        }

        $entry['taxId'] = $taxModel->getId();

        $vatRateIdentity = $this->identityService->findOneBy([
            'adapterIdentifier' => (string) $entry['taxId'],
            'adapterName' => ShopwareAdapter::NAME,
            'objectType' => VatRate::TYPE,
        ]);

        if (null === $vatRateIdentity) {
            throw new NotFoundException('missing vat rate identity for taxId ' . $entry['taxId']);
        }

        return $vatRateIdentity->getObjectIdentifier();
    }

    /**
     * @param int $mode
     *
     * @return int
     */
    private function getItemType($mode)
    {
        switch ($mode) {
            case self::ITEM_TYPE_ID_VOUCHER:
                return OrderItem::TYPE_VOUCHER;
            case self::ITEM_TYPE_ID_DISCOUNT:
                return OrderItem::TYPE_DISCOUNT;
            case self::ITEM_TYPE_ID_SURCHARGE:
                if (json_decode($this->configService->get('surcharge_as_product'), false)) {
                    return OrderItem::TYPE_PRODUCT;
                }

                return OrderItem::TYPE_PAYMENT_SURCHARGE;
            default:
                return OrderItem::TYPE_PRODUCT;
        }
    }

    /**
     * @param array $entry
     * @param bool  $taxFree
     *
     * @return float|int|mixed
     */
    private function getPrice(array $entry, $taxFree)
    {
        return $taxFree ? $entry['price'] + (($entry['price'] / 100) * $entry['taxRate']) :
            (float) $entry['price'];
    }
}


<?php

namespace ShopwareAdapter\ServiceBus\CommandHandler\Media;

use Shopware\Components\Api\Exception\NotFoundException as MediaNotFoundException;
use Shopware\Components\Api\Manager;
use Shopware\Components\Api\Resource\Media as MediaResource;
use ShopwareAdapter\DataPersister\Attribute\AttributeDataPersisterInterface;
use ShopwareAdapter\DataProvider\Media\MediaDataProviderInterface;
use ShopwareAdapter\Helper\AttributeHelper;
use ShopwareAdapter\RequestGenerator\Media\MediaRequestGeneratorInterface;
use ShopwareAdapter\ShopwareAdapter;
use SystemConnector\IdentityService\IdentityServiceInterface;
use SystemConnector\IdentityService\Struct\Identity;
use SystemConnector\ServiceBus\Command\CommandInterface;
use SystemConnector\ServiceBus\Command\TransferObjectCommand;
use SystemConnector\ServiceBus\CommandHandler\CommandHandlerInterface;
use SystemConnector\ServiceBus\CommandType;
use SystemConnector\TransferObject\Media\Media;

class HandleMediaCommandHandler implements CommandHandlerInterface
{
    /**
     * @var IdentityServiceInterface
     */
    private $identityService;

    /**
     * @var MediaRequestGeneratorInterface
     */
    private $mediaRequestGenerator;

    /**
     * @var MediaDataProviderInterface
     */
    private $mediaDataProvider;

    /**
     * @var AttributeHelper
     */
    private $attributeHelper;

    /**
     * @var AttributeDataPersisterInterface
     */
    private $attributePersister;

    public function __construct(
        IdentityServiceInterface $identityService,
        MediaRequestGeneratorInterface $mediaRequestGenerator,
        MediaDataProviderInterface $mediaDataProvider,
        AttributeHelper $attributeHelper,
        AttributeDataPersisterInterface $attributePersister
    ) {
        $this->identityService = $identityService;
        $this->mediaRequestGenerator = $mediaRequestGenerator;
        $this->mediaDataProvider = $mediaDataProvider;
        $this->attributeHelper = $attributeHelper;
        $this->attributePersister = $attributePersister;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(CommandInterface $command)
    {
        return $command instanceof TransferObjectCommand &&
            $command->getAdapterName() === ShopwareAdapter::NAME &&
            $command->getObjectType() === Media::TYPE &&
            $command->getCommandType() === CommandType::HANDLE;
    }

    /**
     * {@inheritdoc}
     *
     * @param TransferObjectCommand $command
     */
    public function handle(CommandInterface $command)
    {
        /** @var Media $media */
        $media = $command->getPayload();

        if ($media->getHash() === $this->mediaDataProvider->getMediaHashForMediaObject($media)) {
            return true;
        }

        $this->attributeHelper->addFieldAsAttribute($media, 'alternateName');
        $this->attributeHelper->addFieldAsAttribute($media, 'name');
        $this->attributeHelper->addFieldAsAttribute($media, 'filename');
        $this->attributeHelper->addFieldAsAttribute($media, 'hash');

        $identity = $this->identityService->findOneBy([
            'objectIdentifier' => (string) $media->getIdentifier(),
            'objectType' => Media::TYPE,
            'adapterName' => ShopwareAdapter::NAME,
        ]);

        $resource = $this->getMediaResource();

        if (null !== $identity) {
            try {
                $resource->delete($identity->getAdapterIdentifier());
            } catch (MediaNotFoundException $exception) {
                // fail silently
            }

            $identities = $this->identityService->findBy([
                'objectIdentifier' => $identity->getObjectIdentifier(),
                'objectType' => Media::TYPE,
                'adapterIdentifier' => $identity->getAdapterIdentifier(),
                'adapterName' => $identity->getAdapterName(),
            ]);

            array_walk($identities, function (Identity $identity) {
                $this->identityService->remove($identity);
            });
        }

        $params = $this->mediaRequestGenerator->generate($media);
        $mediaModel = $resource->create($params);

        $this->identityService->insert(
            $media->getIdentifier(),
            Media::TYPE,
            (string) $mediaModel->getId(),
            ShopwareAdapter::NAME
        );

        $this->attributePersister->saveMediaAttributes(
            $mediaModel,
            $media->getAttributes()
        );

        return true;
    }

    /**
     * @return MediaResource
     */
    private function getMediaResource()
    {
        // without this reset the entitymanager sometimes the album is not found correctly.
        Shopware()->Container()->reset('models');

        return Manager::getResource('Media');
    }
}


<?php

namespace PlentymarketsAdapter\ReadApi;

use DateTimeImmutable;
use PlentymarketsAdapter\Client\Client;
use PlentymarketsAdapter\Client\Iterator\Iterator;
use PlentymarketsAdapter\Helper\LanguageHelperInterface;
use PlentymarketsAdapter\Helper\VariationHelperInterface;
use PlentymarketsAdapter\ReadApi\Item\Variation as VariationApi;

class Item extends ApiAbstract
{
    /**
     * @var VariationApi
     */
    private $itemsVariationsApi;

    /**
     * @var LanguageHelperInterface
     */
    private $languageHelper;

    /**
     * @var VariationHelperInterface
     */
    private $variationHelper;

    /**
     * @var array
     */
    private static $includes = [
        'itemProperties.valueTexts',
        'itemCrossSelling',
        'itemImages',
        'itemShippingProfiles',
    ];

    public function __construct(
        Client $client,
        VariationApi $itemsVariationsApi,
        LanguageHelperInterface $languageHelper,
        VariationHelperInterface $variationHelper
    ) {
        parent::__construct($client);

        $this->itemsVariationsApi = $itemsVariationsApi;
        $this->languageHelper = $languageHelper;
        $this->variationHelper = $variationHelper;
    }

    /**
     * @param int $productId
     *
     * @return array
     */
    public function findOne($productId)
    {
        $result = $this->client->request('GET', 'items/' . $productId, [
            'lang' => $this->languageHelper->getLanguagesQueryString(),
            'with' => implode(',', self::$includes),
        ]);

        if (empty($result)) {
            return $result;
        }

        $result['variations'] = $this->itemsVariationsApi->findBy([
            'itemId' => $result['id'],
            'plentyId' => implode(',', $this->variationHelper->getMappedPlentyClientIds()),
        ]);

        return $result;
    }

    /**
     * @return Iterator
     */
    public function findAll()
    {
        return $this->client->getIterator('items', [
            'lang' => $this->languageHelper->getLanguagesQueryString(),
            'with' => implode(',', self::$includes),
        ], function (array $elements) {
            $this->addAdditionalData($elements);

            return $elements;
        });
    }

    /**
     * @param DateTimeImmutable $startTimestamp
     * @param DateTimeImmutable $endTimestamp
     *
     * @return Iterator
     */
    public function findChanged(DateTimeImmutable $startTimestamp, DateTimeImmutable $endTimestamp)
    {
        $start = $startTimestamp->format(DATE_W3C);
        $end = $endTimestamp->format(DATE_W3C);

        return $this->client->getIterator('items', [
            'lang' => $this->languageHelper->getLanguagesQueryString(),
            'updatedBetween' => $start . ',' . $end,
            'with' => implode(',', self::$includes),
        ], function (array $elements) {
            $this->addAdditionalData($elements);

            return $elements;
        });
    }

    /**
     * @param DateTimeImmutable $startTimestamp
     * @param DateTimeImmutable $endTimestamp
     *
     * @return Iterator
     */
    public function findChangedVariations(DateTimeImmutable $startTimestamp, DateTimeImmutable $endTimestamp)
    {
        $start = $startTimestamp->format(DATE_W3C);
        $end = $endTimestamp->format(DATE_W3C);

        return $this->client->getIterator('items', [
            'lang' => $this->languageHelper->getLanguagesQueryString(),
            'variationUpdatedBetween' => $start . ',' . $end,
            'with' => implode(',', self::$includes),
        ], function (array $elements) {
            $this->addAdditionalData($elements);

            return $elements;
        });
    }

    /**
     * @param array $elements
     */
    private function addAdditionalData(array &$elements)
    {
        if (empty($elements)) {
            return;
        }

        $items = array_column($elements, 'id');

        $variations = $this->itemsVariationsApi->findBy([
            'itemId' => implode(',', $items),
            'plentyId' => implode(',', $this->variationHelper->getMappedPlentyClientIds()),
        ]);

        foreach ($elements as $key => $element) {
            $elements[$key]['variations'] = array_filter($variations, function (array $variation) use ($element) {
                return $element['id'] === $variation['itemId'];
            });
        }
    }
}