Completed
Pull Request — master (#392)
by Christian
13:36
created

ProductToShop::setPurchasePrice()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 17
nc 3
nop 3
1
<?php
2
/**
3
 * (c) shopware AG <[email protected]>
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace ShopwarePlugins\Connect\Components;
9
10
use Shopware\Bundle\SearchBundle\Sorting\ReleaseDateSorting;
11
use Shopware\Connect\Gateway;
12
use Shopware\Components\Model\CategoryDenormalization;
13
use Shopware\Connect\ProductToShop as ProductToShopBase;
14
use Shopware\Connect\Struct\Product;
15
use Shopware\Models\Article\Article as ProductModel;
16
use Shopware\Models\Article\Article;
17
use Shopware\Models\Article\Detail as DetailModel;
18
use Shopware\Models\Attribute\Article as AttributeModel;
19
use Shopware\Components\Model\ModelManager;
20
use Shopware\Connect\Struct\PriceRange;
21
use Shopware\Connect\Struct\ProductUpdate;
22
use Shopware\CustomModels\Connect\ProductStreamAttribute;
23
use Shopware\Models\Customer\Group;
24
use Shopware\Connect\Struct\Property;
25
use Shopware\Models\ProductStream\ProductStream;
26
use Shopware\Models\Property\Group as PropertyGroup;
27
use Shopware\Models\Property\Option as PropertyOption;
28
use Shopware\Models\Property\Value as PropertyValue;
29
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamRepository;
30
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamService;
31
use ShopwarePlugins\Connect\Components\Translations\LocaleMapper;
32
use ShopwarePlugins\Connect\Components\Gateway\ProductTranslationsGateway;
33
use ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway;
34
use ShopwarePlugins\Connect\Components\Utils\UnitMapper;
35
use Shopware\CustomModels\Connect\Attribute as ConnectAttribute;
36
use Shopware\Models\Article\Image;
37
use Shopware\Models\Article\Supplier;
38
39
/**
40
 * The interface for products imported *from* connect *to* the local shop
41
 *
42
 * @category  Shopware
43
 * @package   Shopware\Plugins\SwagConnect
44
 */
45
class ProductToShop implements ProductToShopBase
46
{
47
    /**
48
     * @var Helper
49
     */
50
    private $helper;
51
52
    /**
53
     * @var ModelManager
54
     */
55
    private $manager;
56
57
    /**
58
     * @var \ShopwarePlugins\Connect\Components\Config
59
     */
60
    private $config;
61
62
    /**
63
     * @var ImageImport
64
     */
65
    private $imageImport;
66
67
    /**
68
     * @var \ShopwarePlugins\Connect\Components\VariantConfigurator
69
     */
70
    private $variantConfigurator;
71
72
    /**
73
     * @var MarketplaceGateway
74
     */
75
    private $marketplaceGateway;
76
77
    /**
78
     * @var ProductTranslationsGateway
79
     */
80
    private $productTranslationsGateway;
81
82
    /**
83
     * @var \Shopware\Models\Shop\Repository
84
     */
85
    private $shopRepository;
86
87
    private $localeRepository;
88
89
    /**
90
     * @var CategoryResolver
91
     */
92
    private $categoryResolver;
93
94
    /**
95
     * @var \Shopware\Connect\Gateway
96
     */
97
    private $connectGateway;
98
99
    /**
100
     * @var \Enlight_Event_EventManager
101
     */
102
    private $eventManager;
103
104
    /**
105
     * @var CategoryDenormalization
106
     */
107
    private $categoryDenormalization;
108
109
    /**
110
     * @param Helper $helper
111
     * @param ModelManager $manager
112
     * @param ImageImport $imageImport
113
     * @param \ShopwarePlugins\Connect\Components\Config $config
114
     * @param VariantConfigurator $variantConfigurator
115
     * @param \ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway $marketplaceGateway
116
     * @param ProductTranslationsGateway $productTranslationsGateway
117
     * @param CategoryResolver $categoryResolver
118
     * @param Gateway $connectGateway
119
     * @param \Enlight_Event_EventManager $eventManager
120
     * @param CategoryDenormalization $categoryDenormalization
121
     */
122
    public function __construct(
123
        Helper $helper,
124
        ModelManager $manager,
125
        ImageImport $imageImport,
126
        Config $config,
127
        VariantConfigurator $variantConfigurator,
128
        MarketplaceGateway $marketplaceGateway,
129
        ProductTranslationsGateway $productTranslationsGateway,
130
        CategoryResolver $categoryResolver,
131
        Gateway $connectGateway,
132
        \Enlight_Event_EventManager $eventManager,
133
        CategoryDenormalization $categoryDenormalization
134
    ) {
135
        $this->helper = $helper;
136
        $this->manager = $manager;
137
        $this->config = $config;
138
        $this->imageImport = $imageImport;
139
        $this->variantConfigurator = $variantConfigurator;
140
        $this->marketplaceGateway = $marketplaceGateway;
141
        $this->productTranslationsGateway = $productTranslationsGateway;
142
        $this->categoryResolver = $categoryResolver;
143
        $this->connectGateway = $connectGateway;
144
        $this->eventManager = $eventManager;
145
        $this->categoryDenormalization = $categoryDenormalization;
146
    }
147
148
    /**
149
     * Start transaction
150
     *
151
     * Starts a transaction, which includes all insertOrUpdate and delete
152
     * operations, as well as the revision updates.
153
     *
154
     * @return void
155
     */
156
    public function startTransaction()
157
    {
158
        $this->manager->getConnection()->beginTransaction();
159
    }
160
161
    /**
162
     * Commit transaction
163
     *
164
     * Commits the transactions, once all operations are queued.
165
     *
166
     * @return void
167
     */
168
    public function commit()
169
    {
170
        $this->manager->getConnection()->commit();
171
    }
172
173
    /**
174
     * Import or update given product
175
     *
176
     * Store product in your shop database as an external product. The
177
     * associated sourceId
178
     *
179
     * @param Product $product
180
     */
181
    public function insertOrUpdate(Product $product)
182
    {
183
        /** @var Product $product */
184
        $product = $this->eventManager->filter(
185
            'Connect_ProductToShop_InsertOrUpdate_Before',
186
            $product
187
        );
188
189
        // todo@dn: Set dummy values and make product inactive
190
        if (empty($product->title) || empty($product->vendor)) {
191
            return;
192
        }
193
194
        $number = $this->generateSKU($product);
195
196
        $detail = $this->helper->getArticleDetailModelByProduct($product);
197
        $detail = $this->eventManager->filter(
198
            'Connect_Merchant_Get_Article_Detail_After',
199
            $detail,
200
            [
201
                'product' => $product,
202
                'subject' => $this
203
            ]
204
        );
205
206
        $isMainVariant = false;
207
        if ($detail === null) {
208
            $active = $this->config->getConfig('activateProductsAutomatically', false) ? true : false;
209
210
            $model = $this->getSWProductModel($product, $active, $isMainVariant);
211
212
            $detail = $this->generateNewDetail($product, $model);
213
        } else {
214
            /** @var Article $model */
215
            $model = $detail->getArticle();
216
            // fix for isMainVariant flag
217
            // in connect attribute table
218
            $mainDetail = $model->getMainDetail();
219
            $isMainVariant = $this->checkIfMainVariant($detail, $mainDetail);
220
221
            $this->updateConfiguratorSetTypeFromProduct($model, $product);
222
223
            $this->cleanUpConfiguratorSet($model, $product);
224
        }
225
226
        $detail->setNumber($number);
227
228
        $this->removeConnectImportedCategories($model);
229
230
        $detailAttribute = $this->getOrCreateAttributeModel($detail, $model);
231
232
        $connectAttribute = $this->helper->getConnectAttributeByModel($detail) ?: new ConnectAttribute;
233
        // configure main variant and groupId
234
        if ($isMainVariant === true) {
235
            $connectAttribute->setIsMainVariant(true);
236
        }
237
        $connectAttribute->setGroupId($product->groupId);
238
239
        list($updateFields, $flag) = $this->getUpdateFields($model, $detail, $connectAttribute, $product);
240
        $this->setPropertiesForNewProducts($updateFields, $model, $detailAttribute, $product);
241
242
        $this->applyProductProperties($model, $product);
0 ignored issues
show
Bug introduced by
It seems like $model can be null; however, applyProductProperties() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
243
244
        $detailAttribute = $this->applyMarketplaceAttributes($detailAttribute, $product);
245
246
        $this->setConnectAttributesFromProduct($connectAttribute, $product);
247
248
        // store product categories to connect attribute
249
        $connectAttribute->setCategory($product->categories);
250
251
        $connectAttribute->setLastUpdateFlag($flag);
252
253
        $this->updateDetailFromProduct($detail, $product);
254
255
        // some shops have feature "sell not in stock",
256
        // then end customer should be able to by the product with stock = 0
257
        $shopConfiguration = $this->connectGateway->getShopConfiguration($product->shopId);
258
        if ($shopConfiguration && $shopConfiguration->sellNotInStock) {
259
            $model->setLastStock(false);
260
        } else {
261
            $model->setLastStock(true);
262
        }
263
264
        $this->detailSetUnit($detail, $product, $detailAttribute);
265
266
        $this->detailSetAttributes($detail, $product);
267
268
        $this->connectAttributeSetLastUpdate($connectAttribute, $product);
269
270
271
        if ($model->getMainDetail() === null) {
272
            $model->setMainDetail($detail);
273
        }
274
275
        if ($detail->getAttribute() === null) {
276
            $detail->setAttribute($detailAttribute);
277
            $detailAttribute->setArticle($model);
278
        }
279
280
        $connectAttribute->setArticle($model);
281
        $connectAttribute->setArticleDetail($detail);
282
283
        $this->eventManager->notify(
284
            'Connect_Merchant_Saving_ArticleAttribute_Before',
285
            [
286
                'subject' => $this,
287
                'connectAttribute' => $connectAttribute
288
            ]
289
        );
290
291
        $categories = $this->categoryResolver->resolve($product->categories);
292
        if (count($categories) > 0) {
293
            $detailAttribute->setConnectMappedCategory(true);
294
        }
295
296
        $this->manager->persist($connectAttribute);
297
        $this->manager->persist($model);
298
        $this->manager->persist($detail);
299
        //article has to be flushed
300
        $this->manager->persist($detailAttribute);
301
        $this->manager->flush();
302
303
        $this->categoryDenormalization($model, $categories);
304
305
        $defaultCustomerGroup = $this->helper->getDefaultCustomerGroup();
306
        // Only set prices, if fixedPrice is active or price updates are configured
307
        if (count($detail->getPrices()) == 0 || $connectAttribute->getFixedPrice() || $updateFields['price']) {
308
            $this->setPrice($model, $detail, $product);
0 ignored issues
show
Bug introduced by
It seems like $model can be null; however, setPrice() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
309
        }
310
        // If the price is not being update, update the purchasePrice anyway
311
        $this->setPurchasePrice($detail, $product->purchasePrice, $defaultCustomerGroup);
312
313
        $this->manager->clear();
314
315
        $this->addArticleTranslations($model, $product);
0 ignored issues
show
Bug introduced by
It seems like $model can be null; however, addArticleTranslations() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
316
317
        //clear cache for that article
318
        $this->helper->clearArticleCache($model->getId());
319
320
        if ($updateFields['image']) {
321
            // Reload the model in order to not to work an the already flushed model
322
            $model = $this->helper->getArticleModelByProduct($product);
323
            // import only global images for article
324
            $this->imageImport->importImagesForArticle(array_diff($product->images, $product->variantImages), $model);
0 ignored issues
show
Bug introduced by
It seems like $model defined by $this->helper->getArticleModelByProduct($product) on line 322 can be null; however, ShopwarePlugins\Connect\...mportImagesForArticle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
325
            // Reload the article detail model in order to not to work an the already flushed model
326
            $detail = $this->helper->getArticleDetailModelByProduct($product);
327
            // import only specific images for variant
328
            $this->imageImport->importImagesForDetail($product->variantImages, $detail);
0 ignored issues
show
Bug introduced by
It seems like $detail defined by $this->helper->getArticl...odelByProduct($product) on line 326 can be null; however, ShopwarePlugins\Connect\...importImagesForDetail() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
329
        }
330
        $this->categoryResolver->storeRemoteCategories($product->categories, $model->getId());
331
332
        $this->eventManager->notify(
333
            'Connect_ProductToShop_InsertOrUpdate_After',
334
            [
335
                'connectProduct' => $product,
336
                'shopArticleDetail' => $detail
337
            ]
338
        );
339
340
        $stream = $this->getOrCreateStream($product);
341
        $this->addProductToStream($stream, $model);
0 ignored issues
show
Bug introduced by
It seems like $model can be null; however, addProductToStream() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
342
    }
343
344
    /**
345
     * @param Product $product
346
     * @return string
347
     */
348
    private function generateSKU(Product $product)
349
    {
350
        if (!empty($product->sku)) {
351
            $number = 'SC-' . $product->shopId . '-' . $product->sku;
352
            $duplicatedDetail = $this->helper->getDetailByNumber($number);
353
            if ($duplicatedDetail
354
                && $this->helper->getConnectAttributeByModel($duplicatedDetail)->getSourceId() != $product->sourceId
355
            ) {
356
                $this->deleteDetail($duplicatedDetail);
357
            }
358
        } else {
359
            $number = 'SC-' . $product->shopId . '-' . $product->sourceId;
360
        }
361
362
        return $number;
363
    }
364
365
    /**
366
     * @param DetailModel $detailModel
367
     */
368
    private function deleteDetail(DetailModel $detailModel)
369
    {
370
        $this->eventManager->notify(
371
            'Connect_Merchant_Delete_Product_Before',
372
            [
373
                'subject' => $this,
374
                'articleDetail' => $detailModel
375
            ]
376
        );
377
378
        $article = $detailModel->getArticle();
379
        // Not sure why, but the Attribute can be NULL
380
        $attribute = $this->helper->getConnectAttributeByModel($detailModel);
381
        $this->manager->remove($detailModel);
382
383
        if ($attribute) {
384
            $this->manager->remove($attribute);
385
        }
386
387
        // if removed variant is main variant
388
        // find first variant which is not main and mark it
389
        if ($detailModel->getKind() === 1) {
390
            /** @var \Shopware\Models\Article\Detail $variant */
391
            foreach ($article->getDetails() as $variant) {
392
                if ($variant->getId() != $detailModel->getId()) {
393
                    $variant->setKind(1);
394
                    $article->setMainDetail($variant);
395
                    $connectAttribute = $this->helper->getConnectAttributeByModel($variant);
396
                    if (!$connectAttribute) {
397
                        continue;
398
                    }
399
                    $connectAttribute->setIsMainVariant(true);
400
                    $this->manager->persist($connectAttribute);
401
                    $this->manager->persist($article);
402
                    $this->manager->persist($variant);
403
                    break;
404
                }
405
            }
406
        }
407
408
        if (count($details = $article->getDetails()) === 1) {
409
            $details->clear();
410
            $this->manager->remove($article);
411
        }
412
413
        // Do not remove flush. It's needed when remove article,
414
        // because duplication of ordernumber. Even with remove before
415
        // persist calls mysql throws exception "Duplicate entry"
416
        $this->manager->flush();
417
        // always clear entity manager, because $article->getDetails() returns
418
        // more than 1 detail, but all of them were removed except main one.
419
        $this->manager->clear();
420
    }
421
422
    /**
423
     * @param Product $product
424
     * @param $active
425
     * @param $isMainVariant
426
     * @return null|Article
427
     */
428
    private function getSWProductModel(Product $product, $active, &$isMainVariant)
429
    {
430
        if ($product->groupId !== null) {
431
            $model = $this->helper->getArticleByRemoteProduct($product);
432
            if (!$model instanceof \Shopware\Models\Article\Article) {
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Article does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
433
                $model = $this->helper->createProductModel($product);
434
                $model->setActive($active);
435
                $isMainVariant = true;
436
            }
437
        } else {
438
            $model = $this->helper->getConnectArticleModel($product->sourceId, $product->shopId);
439
            if (!$model instanceof \Shopware\Models\Article\Article) {
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Article does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
440
                $model = $this->helper->createProductModel($product);
441
                $model->setActive($active);
442
            }
443
        }
444
445
        return $model;
446
    }
447
448
    /**
449
     * @param Product $product
450
     * @param $model
451
     * @return DetailModel
452
     */
453
    private function generateNewDetail(Product $product, $model)
454
    {
455
        $detail = new DetailModel();
456
        $detail->setActive($model->getActive());
457
        $this->manager->persist($detail);
458
        $detail->setArticle($model);
459
        $model->getDetails()->add($detail);
460
        if (!empty($product->variant)) {
461
            $this->variantConfigurator->configureVariantAttributes($product, $detail);
462
        }
463
464
        return $detail;
465
    }
466
467
    /**
468
     * @param $detail
469
     * @param $mainDetail
470
     * @return bool
471
     */
472
    private function checkIfMainVariant($detail, $mainDetail)
473
    {
474
        return $detail->getId() === $mainDetail->getId();
475
    }
476
477
    /**
478
     * @param $model
479
     * @param Product $product
480
     */
481
    private function updateConfiguratorSetTypeFromProduct($model, Product $product)
482
    {
483
        if (!empty($product->variant)) {
484
            $configSet = $model->getConfiguratorSet();
485
            $configSet->setType($product->configuratorSetType);
486
        }
487
    }
488
489
    /**
490
     * @param $model
491
     * @param Product $product
492
     */
493
    private function cleanUpConfiguratorSet($model, Product $product)
494
    {
495
        if (empty($product->variant) && $model->getConfiguratorSet()) {
496
            $this->manager->getConnection()->executeQuery(
497
                'UPDATE s_articles SET configurator_set_id = NULL WHERE id = ?',
498
                [$model->getId()]
499
            );
500
        }
501
    }
502
503
    /**
504
     * @param $model
505
     */
506
    private function removeConnectImportedCategories($model)
507
    {
508
        /** @var \Shopware\Models\Category\Category $category */
509
        foreach ($model->getCategories() as $category) {
510
            $attribute = $category->getAttribute();
511
            if (!$attribute) {
512
                continue;
513
            }
514
515
            if ($attribute->getConnectImported()) {
516
                $model->removeCategory($category);
517
            }
518
        }
519
    }
520
521
    /**
522
     * @param $detail
523
     * @param $model
524
     * @return AttributeModel
525
     */
526
    private function getOrCreateAttributeModel($detail, $model)
527
    {
528
        $detailAttribute = $detail->getAttribute();
529
        if (!$detailAttribute) {
530
            $detailAttribute = new AttributeModel();
531
            $detail->setAttribute($detailAttribute);
532
            $model->setAttribute($detailAttribute);
533
            $detailAttribute->setArticle($model);
534
            $detailAttribute->setArticleDetail($detail);
535
        }
536
537
        return $detailAttribute;
538
    }
539
540
    /**
541
     * Get array of update info for the known fields
542
     *
543
     * @param $model
544
     * @param $detail
545
     * @param $attribute
546
     * @param $product
547
     * @return array
548
     */
549
    public function getUpdateFields($model, $detail, $attribute, $product)
550
    {
551
        // This also defines the flags of these fields
552
        $fields = $this->helper->getUpdateFlags();
553
        $flagsByName = array_flip($fields);
554
555
        $flag = 0;
556
        $output = [];
557
        foreach ($fields as $key => $field) {
558
            // Don't handle the imageInitialImport flag
559
            if ($field == 'imageInitialImport') {
560
                continue;
561
            }
562
563
            // If this is a new product
564
            if (!$model->getId() && $field == 'image' && !$this->config->getConfig('importImagesOnFirstImport',
565
                    false)) {
566
                $output[$field] = false;
567
                $flag |= $flagsByName['imageInitialImport'];
568
                continue;
569
            }
570
571
            $updateAllowed = $this->isFieldUpdateAllowed($field, $model, $attribute);
572
            $output[$field] = $updateAllowed;
573
            if (!$updateAllowed && $this->hasFieldChanged($field, $model, $detail, $product)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $updateAllowed of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
574
                $flag |= $key;
575
            }
576
        }
577
578
        return [$output, $flag];
579
    }
580
581
    /**
582
     * Helper method to determine if a given $fields may/must be updated.
583
     * This method will check for the model->id in order to determine, if it is a new entity. Therefore
584
     * this method cannot be used after the model in question was already flushed.
585
     *
586
     * @param $field
587
     * @param $model ProductModel
588
     * @param $attribute ConnectAttribute
589
     * @throws \RuntimeException
590
     * @return bool|null
591
     */
592
    public function isFieldUpdateAllowed($field, ProductModel $model, ConnectAttribute $attribute)
593
    {
594
        $allowed = [
595
            'ShortDescription',
596
            'LongDescription',
597
            'AdditionalDescription',
598
            'Image',
599
            'Price',
600
            'Name',
601
        ];
602
603
        // Always allow updates for new models
604
        if (!$model->getId()) {
605
            return true;
606
        }
607
608
        $field = ucfirst($field);
609
        $attributeGetter = 'getUpdate' . $field;
610
        $configName = 'overwriteProduct' . $field;
611
612
        if (!in_array($field, $allowed)) {
613
            throw new \RuntimeException("Unknown update field {$field}");
614
        }
615
616
        $attributeValue = $attribute->$attributeGetter();
617
618
619
        // If the value is 'null' or 'inherit', the behaviour will be inherited from the global configuration
620
        // Once we have a supplier based configuration, we need to take it into account here
621
        if ($attributeValue == null || $attributeValue == 'inherit') {
622
            return $this->config->getConfig($configName, true);
623
        }
624
625
        return $attributeValue == 'overwrite';
626
    }
627
628
    /**
629
     * Determine if a given field has changed
630
     *
631
     * @param $field
632
     * @param ProductModel $model
633
     * @param DetailModel $detail
634
     * @param Product $product
635
     * @return bool
636
     */
637
    public function hasFieldChanged($field, ProductModel $model, DetailModel $detail, Product $product)
638
    {
639
        switch ($field) {
640
            case 'shortDescription':
641
                return $model->getDescription() != $product->shortDescription;
642
            case 'longDescription':
643
                return $model->getDescriptionLong() != $product->longDescription;
644
            case 'additionalDescription':
645
                return $detail->getAttribute()->getConnectProductDescription() != $product->additionalDescription;
646
            case 'name':
647
                return $model->getName() != $product->title;
648
            case 'image':
649
                return count($model->getImages()) != count($product->images);
650
            case 'price':
651
                $prices = $detail->getPrices();
652
                if (empty($prices)) {
653
                    return true;
654
                }
655
                $price = $prices->first();
656
                if (!$price) {
657
                    return true;
658
                }
659
660
                return $prices->first()->getPrice() != $product->price;
661
        }
662
663
        throw new \InvalidArgumentException('Unrecognized field');
664
    }
665
666
    /**
667
     * @param $updateFields
668
     * @param $model
669
     * @param $detailAttribute
670
     * @param Product $product
671
     */
672
    private function setPropertiesForNewProducts($updateFields, $model, $detailAttribute, Product $product)
673
    {
674
        /*
675
                 * Make sure, that the following properties are set for
676
                 * - new products
677
                 * - products that have been configured to receive these updates
678
                 */
679
        if ($updateFields['name']) {
680
            $model->setName($product->title);
681
        }
682
        if ($updateFields['shortDescription']) {
683
            $model->setDescription($product->shortDescription);
684
        }
685
        if ($updateFields['longDescription']) {
686
            $model->setDescriptionLong($product->longDescription);
687
        }
688
689
        if ($updateFields['additionalDescription']) {
690
            $detailAttribute->setConnectProductDescription($product->additionalDescription);
691
        }
692
693
        if ($product->vat !== null) {
694
            $repo = $this->manager->getRepository('Shopware\Models\Tax\Tax');
695
            $tax = round($product->vat * 100, 2);
696
            /** @var \Shopware\Models\Tax\Tax $tax */
697
            $tax = $repo->findOneBy(['tax' => $tax]);
698
            $model->setTax($tax);
699
        }
700
701
        if ($product->vendor !== null) {
702
            $repo = $this->manager->getRepository('Shopware\Models\Article\Supplier');
703
            $supplier = $repo->findOneBy(['name' => $product->vendor]);
704
            if ($supplier === null) {
705
                $supplier = $this->createSupplier($product->vendor);
706
            }
707
            $model->setSupplier($supplier);
708
        }
709
    }
710
711
    /**
712
     * @param $vendor
713
     * @return Supplier
714
     */
715
    private function createSupplier($vendor)
716
    {
717
        $supplier = new Supplier();
718
719
        if (is_array($vendor)) {
720
            $supplier->setName($vendor['name']);
721
            $supplier->setDescription($vendor['description']);
722
            if (array_key_exists('url', $vendor) && $vendor['url']) {
723
                $supplier->setLink($vendor['url']);
724
            }
725
726
            $supplier->setMetaTitle($vendor['page_title']);
727
728
            if (array_key_exists('logo_url', $vendor) && $vendor['logo_url']) {
729
                $this->imageImport->importImageForSupplier($vendor['logo_url'], $supplier);
730
            }
731
        } else {
732
            $supplier->setName($vendor);
733
        }
734
735
        //sets supplier attributes
736
        $attr = new \Shopware\Models\Attribute\ArticleSupplier();
737
        $attr->setConnectIsRemote(true);
738
739
        $supplier->setAttribute($attr);
740
741
        return $supplier;
742
    }
743
744
    /**
745
     * @param ProductModel $article
746
     * @param Product $product
747
     */
748
    private function applyProductProperties(ProductModel $article, Product $product)
749
    {
750
        if (empty($product->properties)) {
751
            return;
752
        }
753
754
        /** @var Property $firstProperty */
755
        $firstProperty = reset($product->properties);
756
        $groupRepo = $this->manager->getRepository(PropertyGroup::class);
757
        $group = $groupRepo->findOneBy(['name' => $firstProperty->groupName]);
758
759
        if (!$group) {
760
            $group = new PropertyGroup();
761
            $group->setName($firstProperty->groupName);
762
            $group->setComparable($firstProperty->comparable);
763
            $group->setSortMode($firstProperty->sortMode);
764
            $group->setPosition($firstProperty->groupPosition);
765
766
            $attribute = new \Shopware\Models\Attribute\PropertyGroup();
767
            $attribute->setPropertyGroup($group);
768
            $attribute->setConnectIsRemote(true);
769
            $group->setAttribute($attribute);
770
771
            $this->manager->persist($attribute);
772
            $this->manager->persist($group);
773
            $this->manager->flush();
774
        }
775
776
        $propertyValues = $article->getPropertyValues();
777
        $propertyValues->clear();
778
        $this->manager->persist($article);
779
        $this->manager->flush();
780
781
        $article->setPropertyGroup($group);
782
783
        $optionRepo = $this->manager->getRepository(PropertyOption::class);
784
        $valueRepo = $this->manager->getRepository(PropertyValue::class);
785
786
        foreach ($product->properties as $property) {
787
            $option = $optionRepo->findOneBy(['name' => $property->option]);
788
            $optionExists = $option instanceof PropertyOption;
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Property\Option does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
789
            if (!$option) {
790
                $option = new PropertyOption();
791
                $option->setName($property->option);
792
                $option->setFilterable($property->filterable);
793
794
                $attribute = new \Shopware\Models\Attribute\PropertyOption();
795
                $attribute->setPropertyOption($option);
796
                $attribute->setConnectIsRemote(true);
797
                $option->setAttribute($attribute);
798
799
                $this->manager->persist($option);
800
                $this->manager->flush($option);
801
            }
802
803
            if (!$optionExists || !$value = $valueRepo->findOneBy(['option' => $option, 'value' => $property->value])) {
804
                $value = new PropertyValue($option, $property->value);
805
                $value->setPosition($property->valuePosition);
806
807
                $attribute = new \Shopware\Models\Attribute\PropertyValue();
808
                $attribute->setPropertyValue($value);
809
                $attribute->setConnectIsRemote(true);
810
                $value->setAttribute($attribute);
811
812
                $this->manager->persist($value);
813
            }
814
815
            if (!$propertyValues->contains($value)) {
816
                //add only new values
817
                $propertyValues->add($value);
818
            }
819
820
            $filters = [
821
                ['property' => 'options.name', 'expression' => '=', 'value' => $property->option],
822
                ['property' => 'groups.name', 'expression' => '=', 'value' => $property->groupName],
823
            ];
824
825
            $query = $groupRepo->getPropertyRelationQuery($filters, null, 1, 0);
826
            $relation = $query->getOneOrNullResult();
827
828
            if (!$relation) {
829
                $group->addOption($option);
830
                $this->manager->persist($group);
831
                $this->manager->flush($group);
832
            }
833
        }
834
835
        $article->setPropertyValues($propertyValues);
836
837
        $this->manager->persist($article);
838
        $this->manager->flush();
839
    }
840
841
    /**
842
     * Read product attributes mapping and set to shopware attribute model
843
     *
844
     * @param AttributeModel $detailAttribute
845
     * @param Product $product
846
     * @return AttributeModel
847
     */
848
    private function applyMarketplaceAttributes(AttributeModel $detailAttribute, Product $product)
849
    {
850
        $detailAttribute->setConnectReference($product->sourceId);
851
        $detailAttribute->setConnectArticleShipping($product->shipping);
852
        //todo@sb: check if connectAttribute matches position of the marketplace attribute
853
        array_walk($product->attributes, function ($value, $key) use ($detailAttribute) {
854
            $shopwareAttribute = $this->marketplaceGateway->findShopwareMappingFor($key);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $shopwareAttribute is correct as $this->marketplaceGatewa...hopwareMappingFor($key) (which targets ShopwarePlugins\Connect\...indShopwareMappingFor()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
855
            if (strlen($shopwareAttribute) > 0) {
856
                $setter = 'set' . ucfirst($shopwareAttribute);
857
                $detailAttribute->$setter($value);
858
            }
859
        });
860
861
        return $detailAttribute;
862
    }
863
864
    /**
865
     * @param $connectAttribute
866
     * @param Product $product
867
     */
868
    private function setConnectAttributesFromProduct($connectAttribute, Product $product)
869
    {
870
        $connectAttribute->setShopId($product->shopId);
871
        $connectAttribute->setSourceId($product->sourceId);
872
        $connectAttribute->setExportStatus(null);
873
        $connectAttribute->setPurchasePrice($product->purchasePrice);
874
        $connectAttribute->setFixedPrice($product->fixedPrice);
875
        $connectAttribute->setStream($product->stream);
876
877
        // store purchasePriceHash and offerValidUntil
878
        $connectAttribute->setPurchasePriceHash($product->purchasePriceHash);
879
        $connectAttribute->setOfferValidUntil($product->offerValidUntil);
880
    }
881
882
    /**
883
     * @param $detail
884
     * @param Product $product
885
     */
886
    private function updateDetailFromProduct($detail, Product $product)
887
    {
888
        $detail->setInStock($product->availability);
889
        $detail->setEan($product->ean);
890
        $detail->setShippingTime($product->deliveryWorkDays);
891
        $releaseDate = new \DateTime();
892
        $releaseDate->setTimestamp($product->deliveryDate);
893
        $detail->setReleaseDate($releaseDate);
894
        $detail->setMinPurchase($product->minPurchaseQuantity);
895
    }
896
897
    /**
898
     * @param $detail
899
     * @param Product $product
900
     * @param $detailAttribute
901
     */
902
    private function detailSetUnit($detail, Product $product, $detailAttribute)
903
    {
904
        // if connect product has unit
905
        // find local unit with units mapping
906
        // and add to detail model
907
        if (array_key_exists('unit', $product->attributes) && $product->attributes['unit']) {
908
            $detailAttribute->setConnectRemoteUnit($product->attributes['unit']);
909
            if ($this->config->getConfig($product->attributes['unit']) == null) {
910
                $this->config->setConfig($product->attributes['unit'], '', null, 'units');
911
            }
912
913
            /** @var \ShopwarePlugins\Connect\Components\Utils\UnitMapper $unitMapper */
914
            $unitMapper = new UnitMapper($this->config, $this->manager);
915
916
            $shopwareUnit = $unitMapper->getShopwareUnit($product->attributes['unit']);
917
918
            /** @var \Shopware\Models\Article\Unit $unit */
919
            $unit = $this->helper->getUnit($shopwareUnit);
920
            $detail->setUnit($unit);
921
            $detail->setPurchaseUnit($product->attributes['quantity']);
922
            $detail->setReferenceUnit($product->attributes['ref_quantity']);
923
        } else {
924
            $detail->setUnit(null);
925
            $detail->setPurchaseUnit(null);
926
            $detail->setReferenceUnit(null);
927
        }
928
    }
929
930
    /**
931
     * @param $detail
932
     * @param Product $product
933
     */
934
    private function detailSetAttributes($detail, Product $product)
935
    {
936
        // set dimension
937
        if (array_key_exists('dimension', $product->attributes) && $product->attributes['dimension']) {
938
            $dimension = explode('x', $product->attributes['dimension']);
939
            $detail->setLen($dimension[0]);
940
            $detail->setWidth($dimension[1]);
941
            $detail->setHeight($dimension[2]);
942
        } else {
943
            $detail->setLen(null);
944
            $detail->setWidth(null);
945
            $detail->setHeight(null);
946
        }
947
948
        // set weight
949
        if (array_key_exists('weight', $product->attributes) && $product->attributes['weight']) {
950
            $detail->setWeight($product->attributes['weight']);
951
        }
952
953
        //set package unit
954
        if (array_key_exists(Product::ATTRIBUTE_PACKAGEUNIT, $product->attributes)) {
955
            $detail->setPackUnit($product->attributes[Product::ATTRIBUTE_PACKAGEUNIT]);
956
        }
957
958
        //set basic unit
959
        if (array_key_exists(Product::ATTRIBUTE_BASICUNIT, $product->attributes)) {
960
            $detail->setMinPurchase($product->attributes[Product::ATTRIBUTE_BASICUNIT]);
961
        }
962
963
        //set manufacturer no.
964
        if (array_key_exists(Product::ATTRIBUTE_MANUFACTURERNUMBER, $product->attributes)) {
965
            $detail->setSupplierNumber($product->attributes[Product::ATTRIBUTE_MANUFACTURERNUMBER]);
966
        }
967
    }
968
969
    /**
970
     * @param $connectAttribute
971
     * @param Product $product
972
     */
973
    private function connectAttributeSetLastUpdate($connectAttribute, Product $product)
974
    {
975
        // Whenever a product is updated, store a json encoded list of all fields that are updated optionally
976
        // This way a customer will be able to apply the most recent changes any time later
977
        $connectAttribute->setLastUpdate(json_encode([
978
            'shortDescription' => $product->shortDescription,
979
            'longDescription' => $product->longDescription,
980
            'additionalDescription' => $product->additionalDescription,
981
            'purchasePrice' => $product->purchasePrice,
982
            'image' => $product->images,
983
            'variantImages' => $product->variantImages,
984
            'price' => $product->price * ($product->vat + 1),
985
            'name' => $product->title,
986
            'vat' => $product->vat
987
        ]));
988
    }
989
990
    /**
991
     * @param $model
992
     * @param $categories
993
     */
994
    private function categoryDenormalization($model, $categories)
995
    {
996
        $this->categoryDenormalization->disableTransactions();
997
        foreach ($categories as $category) {
998
            $this->categoryDenormalization->addAssignment($model->getId(), $category);
999
            $this->manager->getConnection()->executeQuery(
1000
                'INSERT IGNORE INTO `s_articles_categories` (`articleID`, `categoryID`) VALUES (?,?)',
1001
                [$model->getId(), $category]
1002
            );
1003
        }
1004
        $this->categoryDenormalization->enableTransactions();
1005
    }
1006
1007
    /**
1008
     * Set detail purchase price with plain SQL
1009
     * Entity usage throws exception when error handlers are disabled
1010
     *
1011
     * @param ProductModel $article
1012
     * @param DetailModel $detail
1013
     * @param Product $product
1014
     * @throws \Doctrine\DBAL\DBALException
1015
     */
1016
    private function setPrice(ProductModel $article, DetailModel $detail, Product $product)
1017
    {
1018
        // set price via plain SQL because shopware throws exception
1019
        // undefined index: key when error handler is disabled
1020
        $customerGroup = $this->helper->getDefaultCustomerGroup();
1021
1022
        if (!empty($product->priceRanges)) {
1023
            $this->setPriceRange($article, $detail, $product->priceRanges, $customerGroup);
1024
1025
            return;
1026
        }
1027
1028
        $id = $this->manager->getConnection()->fetchColumn(
1029
            'SELECT id FROM `s_articles_prices`
1030
              WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?',
1031
            [$customerGroup->getKey(), 1, 'beliebig', $article->getId(), $detail->getId()]
1032
        );
1033
1034
        // todo@sb: test update prices
1035
        if ($id > 0) {
1036
            $this->manager->getConnection()->executeQuery(
1037
                'UPDATE `s_articles_prices` SET `price` = ?, `baseprice` = ? WHERE `id` = ?',
1038
                [$product->price, $product->purchasePrice, $id]
1039
            );
1040
        } else {
1041
            $this->manager->getConnection()->executeQuery(
1042
                'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`, `baseprice`)
1043
              VALUES (?, 1, "beliebig", ?, ?, ?, ?);',
1044
                [
1045
                    $customerGroup->getKey(),
1046
                    $article->getId(),
1047
                    $detail->getId(),
1048
                    $product->price,
1049
                    $product->purchasePrice
1050
                ]
1051
            );
1052
        }
1053
    }
1054
1055
    /**
1056
     * @param ProductModel $article
1057
     * @param DetailModel $detail
1058
     * @param array $priceRanges
1059
     * @param Group $group
1060
     * @throws \Doctrine\DBAL\ConnectionException
1061
     * @throws \Exception
1062
     */
1063
    private function setPriceRange(ProductModel $article, DetailModel $detail, array $priceRanges, Group $group)
1064
    {
1065
        $this->manager->getConnection()->beginTransaction();
1066
1067
        try {
1068
            // We always delete the prices,
1069
            // because we can not know which record is update
1070
            $this->manager->getConnection()->executeQuery(
1071
                'DELETE FROM `s_articles_prices` WHERE `articleID` = ? AND `articledetailsID` = ?',
1072
                [$article->getId(), $detail->getId()]
1073
            );
1074
1075
            /** @var PriceRange $priceRange */
1076
            foreach ($priceRanges as $priceRange) {
1077
                $priceTo = $priceRange->to == PriceRange::ANY ? 'beliebig' : $priceRange->to;
1078
1079
                //todo: maybe batch insert if possible?
1080
                $this->manager->getConnection()->executeQuery(
1081
                    'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`)
1082
                      VALUES (?, ?, ?, ?, ?, ?);',
1083
                    [
1084
                        $group->getKey(),
1085
                        $priceRange->from,
1086
                        $priceTo,
1087
                        $article->getId(),
1088
                        $detail->getId(),
1089
                        $priceRange->price
1090
                    ]
1091
                );
1092
            }
1093
            $this->manager->getConnection()->commit();
1094
        } catch (\Exception $e) {
1095
            $this->manager->getConnection()->rollBack();
1096
            throw new \Exception($e->getMessage());
1097
        }
1098
    }
1099
1100
    /**
1101
     * Set detail purchase price with plain SQL
1102
     * Entity usage throws exception when error handlers are disabled
1103
     *
1104
     * @param DetailModel $detail
1105
     * @param float $purchasePrice
1106
     * @param Group $defaultGroup
1107
     * @throws \Doctrine\DBAL\DBALException
1108
     */
1109
    private function setPurchasePrice(DetailModel $detail, $purchasePrice, Group $defaultGroup)
1110
    {
1111
        if (method_exists($detail, 'setPurchasePrice')) {
1112
            $this->manager->getConnection()->executeQuery(
1113
                'UPDATE `s_articles_details` SET `purchaseprice` = ? WHERE `id` = ?',
1114
                [$purchasePrice, $detail->getId()]
1115
            );
1116
        } else {
1117
            $id = $this->manager->getConnection()->fetchColumn(
1118
                'SELECT id FROM `s_articles_prices`
1119
              WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?',
1120
                [$defaultGroup->getKey(), 1, 'beliebig', $detail->getArticleId(), $detail->getId()]
1121
            );
1122
1123
            if ($id > 0) {
1124
                $this->manager->getConnection()->executeQuery(
1125
                    'UPDATE `s_articles_prices` SET `baseprice` = ? WHERE `id` = ?',
1126
                    [$purchasePrice, $id]
1127
                );
1128
            } else {
1129
                $this->manager->getConnection()->executeQuery(
1130
                    'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `baseprice`)
1131
              VALUES (?, 1, "beliebig", ?, ?, ?);',
1132
                    [$defaultGroup->getKey(), $detail->getArticleId(), $detail->getId(), $purchasePrice]
1133
                );
1134
            }
1135
        }
1136
    }
1137
1138
    /**
1139
     * Adds translation record for given article
1140
     *
1141
     * @param ProductModel $article
1142
     * @param Product $sdkProduct
1143
     */
1144
    private function addArticleTranslations(ProductModel $article, Product $sdkProduct)
1145
    {
1146
        /** @var \Shopware\Connect\Struct\Translation $translation */
1147
        foreach ($sdkProduct->translations as $key => $translation) {
1148
            /** @var \Shopware\Models\Shop\Locale $locale */
1149
            $locale = $this->getLocaleRepository()->findOneBy(['locale' => LocaleMapper::getShopwareLocale($key)]);
1150
            /** @var \Shopware\Models\Shop\Shop $shop */
1151
            $shop = $this->getShopRepository()->findOneBy(['locale' => $locale]);
1152
            if (!$shop) {
1153
                continue;
1154
            }
1155
1156
            $this->productTranslationsGateway->addArticleTranslation($translation, $article->getId(), $shop->getId());
1157
        }
1158
    }
1159
1160
    /**
1161
     * dsadsa
1162
     * @return \Shopware\Components\Model\ModelRepository
1163
     */
1164
    private function getLocaleRepository()
1165
    {
1166
        if (!$this->localeRepository) {
1167
            $this->localeRepository = $this->manager->getRepository('Shopware\Models\Shop\Locale');
1168
        }
1169
1170
        return $this->localeRepository;
1171
    }
1172
1173
    private function getShopRepository()
1174
    {
1175
        if (!$this->shopRepository) {
1176
            $this->shopRepository = $this->manager->getRepository('Shopware\Models\Shop\Shop');
1177
        }
1178
1179
        return $this->shopRepository;
1180
    }
1181
1182
    /**
1183
     * @param Product $product
1184
     * @return ProductStream
1185
     */
1186
    private function getOrCreateStream(Product $product)
1187
    {
1188
        /** @var ProductStreamRepository $repo */
1189
        $repo = $this->manager->getRepository(ProductStreamAttribute::class);
1190
        $stream = $repo->findConnectByName($product->stream);
1191
1192
        if (!$stream) {
1193
            $stream = new ProductStream();
1194
            $stream->setName($product->stream);
1195
            $stream->setType(ProductStreamService::STATIC_STREAM);
1196
            $stream->setSorting(json_encode(
1197
                [ReleaseDateSorting::class => ['direction' => 'desc']]
1198
            ));
1199
1200
            //add attributes
1201
            $attribute = new \Shopware\Models\Attribute\ProductStream();
1202
            $attribute->setProductStream($stream);
1203
            $attribute->setConnectIsRemote(true);
1204
            $stream->setAttribute($attribute);
1205
1206
            $this->manager->persist($attribute);
1207
            $this->manager->persist($stream);
1208
            $this->manager->flush();
1209
        }
1210
1211
        return $stream;
1212
    }
1213
1214
    /**
1215
     * @param ProductStream $stream
1216
     * @param ProductModel $article
1217
     * @throws \Doctrine\DBAL\DBALException
1218
     */
1219
    private function addProductToStream(ProductStream $stream, ProductModel $article)
1220
    {
1221
        $conn = $this->manager->getConnection();
1222
        $sql = 'INSERT INTO `s_product_streams_selection` (`stream_id`, `article_id`)
1223
                VALUES (:streamId, :articleId)
1224
                ON DUPLICATE KEY UPDATE stream_id = :streamId, article_id = :articleId';
1225
        $stmt = $conn->prepare($sql);
1226
        $stmt->execute([':streamId' => $stream->getId(), ':articleId' => $article->getId()]);
1227
    }
1228
1229
    /**
1230
     * Delete product or product variant with given shopId and sourceId.
1231
     *
1232
     * Only the combination of both identifies a product uniquely. Do NOT
1233
     * delete products just by their sourceId.
1234
     *
1235
     * You might receive delete requests for products, which are not available
1236
     * in your shop. Just ignore them.
1237
     *
1238
     * @param string $shopId
1239
     * @param string $sourceId
1240
     * @return void
1241
     */
1242
    public function delete($shopId, $sourceId)
1243
    {
1244
        $detail = $this->helper->getArticleDetailModelByProduct(new Product([
1245
            'shopId' => $shopId,
1246
            'sourceId' => $sourceId,
1247
        ]));
1248
        if ($detail === null) {
1249
            return;
1250
        }
1251
1252
        $this->deleteDetail($detail);
1253
    }
1254
1255
    public function update($shopId, $sourceId, ProductUpdate $product)
1256
    {
1257
        // find article detail id
1258
        $articleDetailId = $this->manager->getConnection()->fetchColumn(
1259
            'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?',
1260
            [$sourceId, $shopId]
1261
        );
1262
1263
        $this->eventManager->notify(
1264
            'Connect_Merchant_Update_GeneralProductInformation',
1265
            [
1266
                'subject' => $this,
1267
                'shopId' => $shopId,
1268
                'sourceId' => $sourceId,
1269
                'articleDetailId' => $articleDetailId
1270
            ]
1271
        );
1272
1273
        // update purchasePriceHash, offerValidUntil and purchasePrice in connect attribute
1274
        $this->manager->getConnection()->executeUpdate(
1275
            'UPDATE s_plugin_connect_items SET purchase_price_hash = ?, offer_valid_until = ?, purchase_price = ?
1276
            WHERE source_id = ? AND shop_id = ?',
1277
            [
1278
                $product->purchasePriceHash,
1279
                $product->offerValidUntil,
1280
                $product->purchasePrice,
1281
                $sourceId,
1282
                $shopId,
1283
            ]
1284
        );
1285
1286
        // update stock in article detail
1287
        // update prices
1288
        // if purchase price is stored in article detail
1289
        // update it together with stock
1290
        // since shopware 5.2
1291
        if (method_exists('Shopware\Models\Article\Detail', 'getPurchasePrice')) {
1292
            $this->manager->getConnection()->executeUpdate(
1293
                'UPDATE s_articles_details SET instock = ?, purchaseprice = ? WHERE id = ?',
1294
                [$product->availability, $product->purchasePrice, $articleDetailId]
1295
            );
1296
        } else {
1297
            $this->manager->getConnection()->executeUpdate(
1298
                'UPDATE s_articles_details SET instock = ? WHERE id = ?',
1299
                [$product->availability, $articleDetailId]
1300
            );
1301
        }
1302
        $this->manager->getConnection()->executeUpdate(
1303
            "UPDATE s_articles_prices SET price = ?, baseprice = ? WHERE articledetailsID = ? AND pricegroup = 'EK'",
1304
            [$product->price, $product->purchasePrice, $articleDetailId]
1305
        );
1306
    }
1307
1308
    public function changeAvailability($shopId, $sourceId, $availability)
1309
    {
1310
        // find article detail id
1311
        $articleDetailId = $this->manager->getConnection()->fetchColumn(
1312
            'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?',
1313
            [$sourceId, $shopId]
1314
        );
1315
1316
        $this->eventManager->notify(
1317
            'Connect_Merchant_Update_GeneralProductInformation',
1318
            [
1319
                'subject' => $this,
1320
                'shopId' => $shopId,
1321
                'sourceId' => $sourceId,
1322
                'articleDetailId' => $articleDetailId
1323
            ]
1324
        );
1325
1326
        // update stock in article detail
1327
        $this->manager->getConnection()->executeUpdate(
1328
            'UPDATE s_articles_details SET instock = ? WHERE id = ?',
1329
            [$availability, $articleDetailId]
1330
        );
1331
    }
1332
1333
    /**
1334
     * @inheritDoc
1335
     */
1336
    public function makeMainVariant($shopId, $sourceId, $groupId)
1337
    {
1338
        //find article detail which should be selected as main one
1339
        $newMainDetail = $this->helper->getConnectArticleDetailModel($sourceId, $shopId);
1340
        if (!$newMainDetail) {
1341
            return;
1342
        }
1343
1344
        /** @var \Shopware\Models\Article\Article $article */
1345
        $article = $newMainDetail->getArticle();
1346
1347
        $this->eventManager->notify(
1348
            'Connect_Merchant_Update_ProductMainVariant_Before',
1349
            [
1350
                'subject' => $this,
1351
                'shopId' => $shopId,
1352
                'sourceId' => $sourceId,
1353
                'articleId' => $article->getId(),
1354
                'articleDetailId' => $newMainDetail->getId()
1355
            ]
1356
        );
1357
1358
        // replace current main detail with new one
1359
        $currentMainDetail = $article->getMainDetail();
1360
        $currentMainDetail->setKind(2);
1361
        $newMainDetail->setKind(1);
1362
        $article->setMainDetail($newMainDetail);
1363
1364
        $this->manager->persist($newMainDetail);
1365
        $this->manager->persist($currentMainDetail);
1366
        $this->manager->persist($article);
1367
        $this->manager->flush();
1368
    }
1369
}
1370