Completed
Pull Request — master (#392)
by Jonas
03:47
created

ProductToShop::checkIfMainVariant()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
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\OrderStatus;
15
use Shopware\Connect\Struct\Product;
16
use Shopware\Models\Article\Article as ProductModel;
17
use Shopware\Models\Order\Order;
18
use Shopware\Models\Order\Status;
19
use Shopware\Models\Article\Detail as DetailModel;
20
use Shopware\Models\Attribute\Article as AttributeModel;
21
use Shopware\Components\Model\ModelManager;
22
use Shopware\Connect\Struct\PriceRange;
23
use Shopware\Connect\Struct\ProductUpdate;
24
use Shopware\CustomModels\Connect\ProductStreamAttribute;
25
use Shopware\Models\Customer\Group;
26
use Shopware\Connect\Struct\Property;
27
use Shopware\Models\ProductStream\ProductStream;
28
use Shopware\Models\Property\Group as PropertyGroup;
29
use Shopware\Models\Property\Option as PropertyOption;
30
use Shopware\Models\Property\Value as PropertyValue;
31
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamRepository;
32
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamService;
33
use ShopwarePlugins\Connect\Components\Translations\LocaleMapper;
34
use ShopwarePlugins\Connect\Components\Gateway\ProductTranslationsGateway;
35
use ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway;
36
use ShopwarePlugins\Connect\Components\Utils\UnitMapper;
37
use Shopware\CustomModels\Connect\Attribute as ConnectAttribute;
38
use Shopware\Models\Article\Image;
39
use Shopware\Models\Article\Supplier;
40
use Shopware\Models\Tax\Tax;
41
42
/**
43
 * The interface for products imported *from* connect *to* the local shop
44
 *
45
 * @category  Shopware
46
 * @package   Shopware\Plugins\SwagConnect
47
 */
48
class ProductToShop implements ProductToShopBase
49
{
50
    /**
51
     * @var Helper
52
     */
53
    private $helper;
54
55
    /**
56
     * @var ModelManager
57
     */
58
    private $manager;
59
60
    /**
61
     * @var \ShopwarePlugins\Connect\Components\Config
62
     */
63
    private $config;
64
65
    /**
66
     * @var ImageImport
67
     */
68
    private $imageImport;
69
70
    /**
71
     * @var \ShopwarePlugins\Connect\Components\VariantConfigurator
72
     */
73
    private $variantConfigurator;
74
75
    /**
76
     * @var MarketplaceGateway
77
     */
78
    private $marketplaceGateway;
79
80
    /**
81
     * @var ProductTranslationsGateway
82
     */
83
    private $productTranslationsGateway;
84
85
    /**
86
     * @var \Shopware\Models\Shop\Repository
87
     */
88
    private $shopRepository;
89
90
    private $localeRepository;
91
92
    /**
93
     * @var CategoryResolver
94
     */
95
    private $categoryResolver;
96
97
    /**
98
     * @var \Shopware\Connect\Gateway
99
     */
100
    private $connectGateway;
101
102
    /**
103
     * @var \Enlight_Event_EventManager
104
     */
105
    private $eventManager;
106
107
    /**
108
     * @var CategoryDenormalization
109
     */
110
    private $categoryDenormalization;
111
112
    /**
113
     * @param Helper $helper
114
     * @param ModelManager $manager
115
     * @param ImageImport $imageImport
116
     * @param \ShopwarePlugins\Connect\Components\Config $config
117
     * @param VariantConfigurator $variantConfigurator
118
     * @param \ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway $marketplaceGateway
119
     * @param ProductTranslationsGateway $productTranslationsGateway
120
     * @param CategoryResolver $categoryResolver
121
     * @param Gateway $connectGateway
122
     * @param \Enlight_Event_EventManager $eventManager
123
     * @param CategoryDenormalization $categoryDenormalization
124
     */
125
    public function __construct(
126
        Helper $helper,
127
        ModelManager $manager,
128
        ImageImport $imageImport,
129
        Config $config,
130
        VariantConfigurator $variantConfigurator,
131
        MarketplaceGateway $marketplaceGateway,
132
        ProductTranslationsGateway $productTranslationsGateway,
133
        CategoryResolver $categoryResolver,
134
        Gateway $connectGateway,
135
        \Enlight_Event_EventManager $eventManager,
136
        CategoryDenormalization $categoryDenormalization
137
    ) {
138
        $this->helper = $helper;
139
        $this->manager = $manager;
140
        $this->config = $config;
141
        $this->imageImport = $imageImport;
142
        $this->variantConfigurator = $variantConfigurator;
143
        $this->marketplaceGateway = $marketplaceGateway;
144
        $this->productTranslationsGateway = $productTranslationsGateway;
145
        $this->categoryResolver = $categoryResolver;
146
        $this->connectGateway = $connectGateway;
147
        $this->eventManager = $eventManager;
148
        $this->categoryDenormalization = $categoryDenormalization;
149
    }
150
151
    /**
152
     * Start transaction
153
     *
154
     * Starts a transaction, which includes all insertOrUpdate and delete
155
     * operations, as well as the revision updates.
156
     *
157
     * @return void
158
     */
159
    public function startTransaction()
160
    {
161
        $this->manager->getConnection()->beginTransaction();
162
    }
163
164
    /**
165
     * Commit transaction
166
     *
167
     * Commits the transactions, once all operations are queued.
168
     *
169
     * @return void
170
     */
171
    public function commit()
172
    {
173
        $this->manager->getConnection()->commit();
174
    }
175
176
    /**
177
     * Import or update given product
178
     *
179
     * Store product in your shop database as an external product. The
180
     * associated sourceId
181
     *
182
     * @param Product $product
183
     */
184
    public function insertOrUpdate(Product $product)
185
    {
186
        /** @var Product $product */
187
        $product = $this->eventManager->filter(
188
            'Connect_ProductToShop_InsertOrUpdate_Before',
189
            $product
190
        );
191
192
        // todo@dn: Set dummy values and make product inactive
193
        if (empty($product->title) || empty($product->vendor)) {
194
            return;
195
        }
196
197
        $number = $this->generateSKU($product);
198
199
        $detail = $this->helper->getArticleDetailModelByProduct($product);
200
        $detail = $this->eventManager->filter(
201
            'Connect_Merchant_Get_Article_Detail_After',
202
            $detail,
203
            [
204
                'product' => $product,
205
                'subject' => $this
206
            ]
207
        );
208
209
        $isMainVariant = false;
210
        if ($detail === null) {
211
            $active = $this->config->getConfig('activateProductsAutomatically', false) ? true : false;
212
213
            $model = $this->getSWProductModel($product, $active, $isMainVariant);
214
215
            $detail = $this->generateNewDetail($product, $model);
216
        } else {
217
            /** @var ProductModel $model */
218
            $model = $detail->getArticle();
219
            // fix for isMainVariant flag
220
            // in connect attribute table
221
            $mainDetail = $model->getMainDetail();
222
            $isMainVariant = $this->checkIfMainVariant($detail, $mainDetail);
223
224
            $this->updateConfiguratorSetTypeFromProduct($model, $product);
225
226
            $this->cleanUpConfiguratorSet($model, $product);
227
        }
228
229
        $detail->setNumber($number);
230
231
        $this->removeConnectImportedCategories($model);
232
233
        $detailAttribute = $this->getOrCreateAttributeModel($detail, $model);
234
235
        $connectAttribute = $this->helper->getConnectAttributeByModel($detail) ?: new ConnectAttribute;
236
        // configure main variant and groupId
237
        if ($isMainVariant === true) {
238
            $connectAttribute->setIsMainVariant(true);
239
        }
240
        $connectAttribute->setGroupId($product->groupId);
241
242
        list($updateFields, $flag) = $this->getUpdateFields($model, $detail, $connectAttribute, $product);
243
        $this->setPropertiesForNewProducts($updateFields, $model, $detailAttribute, $product);
244
245
        $this->saveVat($product, $model);
246
247
        $this->applyProductProperties($model, $product);
248
249
        $detailAttribute = $this->applyMarketplaceAttributes($detailAttribute, $product);
250
251
        $this->setConnectAttributesFromProduct($connectAttribute, $product);
252
253
        // store product categories to connect attribute
254
        $connectAttribute->setCategory($product->categories);
255
256
        $connectAttribute->setLastUpdateFlag($flag);
257
258
        $connectAttribute->setPurchasePriceHash($product->purchasePriceHash);
259
        $connectAttribute->setOfferValidUntil($product->offerValidUntil);
260
261
        $this->updateDetailFromProduct($detail, $product);
262
263
        // some shops have feature "sell not in stock",
264
        // then end customer should be able to by the product with stock = 0
265
        $shopConfiguration = $this->connectGateway->getShopConfiguration($product->shopId);
266
        if ($shopConfiguration && $shopConfiguration->sellNotInStock) {
267
            $model->setLastStock(false);
268
        } else {
269
            $model->setLastStock(true);
270
        }
271
272
        $this->detailSetUnit($detail, $product, $detailAttribute);
273
274
        $this->detailSetAttributes($detail, $product);
275
276
        $this->connectAttributeSetLastUpdate($connectAttribute, $product);
277
278
        if ($model->getMainDetail() === null) {
279
            $model->setMainDetail($detail);
280
        }
281
282
        if ($detail->getAttribute() === null) {
283
            $detail->setAttribute($detailAttribute);
284
            $detailAttribute->setArticle($model);
285
        }
286
287
        $connectAttribute->setArticle($model);
288
        $connectAttribute->setArticleDetail($detail);
289
290
        $this->eventManager->notify(
291
            'Connect_Merchant_Saving_ArticleAttribute_Before',
292
            [
293
                'subject' => $this,
294
                'connectAttribute' => $connectAttribute
295
            ]
296
        );
297
298
        $this->manager->persist($model);
299
        $this->manager->flush();
300
301
        $this->categoryResolver->storeRemoteCategories($product->categories, $model->getId());
302
        $categories = $this->categoryResolver->resolve($product->categories);
303
        if (count($categories) > 0) {
304
            $detailAttribute->setConnectMappedCategory(true);
305
        }
306
307
        $this->manager->persist($connectAttribute);
308
        $this->manager->persist($detail);
309
        //article has to be flushed
310
        $this->manager->persist($detailAttribute);
311
        $this->manager->flush();
312
313
        $this->categoryDenormalization($model, $categories);
314
315
        $defaultCustomerGroup = $this->helper->getDefaultCustomerGroup();
316
        // Only set prices, if fixedPrice is active or price updates are configured
317
        if (count($detail->getPrices()) == 0 || $connectAttribute->getFixedPrice() || $updateFields['price']) {
318
            $this->setPrice($model, $detail, $product);
319
        }
320
        // If the price is not being update, update the purchasePrice anyway
321
        $this->setPurchasePrice($detail, $product->purchasePrice, $defaultCustomerGroup);
322
323
        $this->manager->clear();
324
325
        $this->addArticleTranslations($model, $product);
326
327
        //clear cache for that article
328
        $this->helper->clearArticleCache($model->getId());
329
330
        if ($updateFields['image']) {
331
            // Reload the model in order to not to work an the already flushed model
332
            $model = $this->helper->getArticleModelByProduct($product);
333
            // import only global images for article
334
            $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 332 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...
335
            // Reload the article detail model in order to not to work an the already flushed model
336
            $detail = $this->helper->getArticleDetailModelByProduct($product);
337
            // import only specific images for variant
338
            $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 336 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...
339
        }
340
341
        $this->eventManager->notify(
342
            'Connect_ProductToShop_InsertOrUpdate_After',
343
            [
344
                'connectProduct' => $product,
345
                'shopArticleDetail' => $detail
346
            ]
347
        );
348
349
        $stream = $this->getOrCreateStream($product);
350
        $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...
351
    }
352
353
    /**
354
     * @param Product $product
355
     * @return string
356
     */
357
    private function generateSKU(Product $product)
358
    {
359
        if (!empty($product->sku)) {
360
            $number = 'SC-' . $product->shopId . '-' . $product->sku;
361
            $duplicatedDetail = $this->helper->getDetailByNumber($number);
362
            if ($duplicatedDetail
363
                && $this->helper->getConnectAttributeByModel($duplicatedDetail)->getSourceId() != $product->sourceId
364
            ) {
365
                $this->deleteDetail($duplicatedDetail);
366
            }
367
        } else {
368
            $number = 'SC-' . $product->shopId . '-' . $product->sourceId;
369
        }
370
371
        return $number;
372
    }
373
374
    /**
375
     * @param DetailModel $detailModel
376
     */
377
    private function deleteDetail(DetailModel $detailModel)
378
    {
379
        $this->eventManager->notify(
380
            'Connect_Merchant_Delete_Product_Before',
381
            [
382
                'subject' => $this,
383
                'articleDetail' => $detailModel
384
            ]
385
        );
386
387
        $article = $detailModel->getArticle();
388
        // Not sure why, but the Attribute can be NULL
389
        $attribute = $this->helper->getConnectAttributeByModel($detailModel);
390
        $this->manager->remove($detailModel);
391
392
        if ($attribute) {
393
            $this->manager->remove($attribute);
394
        }
395
396
        // if removed variant is main variant
397
        // find first variant which is not main and mark it
398
        if ($detailModel->getKind() === 1) {
399
            /** @var \Shopware\Models\Article\Detail $variant */
400
            foreach ($article->getDetails() as $variant) {
401
                if ($variant->getId() != $detailModel->getId()) {
402
                    $variant->setKind(1);
403
                    $article->setMainDetail($variant);
404
                    $connectAttribute = $this->helper->getConnectAttributeByModel($variant);
405
                    if (!$connectAttribute) {
406
                        continue;
407
                    }
408
                    $connectAttribute->setIsMainVariant(true);
409
                    $this->manager->persist($connectAttribute);
410
                    $this->manager->persist($article);
411
                    $this->manager->persist($variant);
412
                    break;
413
                }
414
            }
415
        }
416
417
        if (count($details = $article->getDetails()) === 1) {
418
            $details->clear();
419
            $this->manager->remove($article);
420
        }
421
422
        // Do not remove flush. It's needed when remove article,
423
        // because duplication of ordernumber. Even with remove before
424
        // persist calls mysql throws exception "Duplicate entry"
425
        $this->manager->flush();
426
        // always clear entity manager, because $article->getDetails() returns
427
        // more than 1 detail, but all of them were removed except main one.
428
        $this->manager->clear();
429
    }
430
431
    /**
432
     * @param Product $product
433
     * @param $active
434
     * @param $isMainVariant
435
     * @return null|Article
436
     */
437
    private function getSWProductModel(Product $product, $active, &$isMainVariant)
438
    {
439
        if ($product->groupId !== null) {
440
            $model = $this->helper->getArticleByRemoteProduct($product);
441
            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...
442
                $model = $this->helper->createProductModel($product);
443
                $model->setActive($active);
444
                $isMainVariant = true;
445
            }
446
        } else {
447
            $model = $this->helper->getConnectArticleModel($product->sourceId, $product->shopId);
448
            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...
449
                $model = $this->helper->createProductModel($product);
450
                $model->setActive($active);
451
            }
452
        }
453
454
        return $model;
455
    }
456
457
    /**
458
     * @param Product $product
459
     * @param $model
460
     * @return DetailModel
461
     */
462
    private function generateNewDetail(Product $product, $model)
463
    {
464
        $detail = new DetailModel();
465
        $detail->setActive($model->getActive());
466
        $this->manager->persist($detail);
467
        $detail->setArticle($model);
468
        $model->getDetails()->add($detail);
469
        if (!empty($product->variant)) {
470
            $this->variantConfigurator->configureVariantAttributes($product, $detail);
471
        }
472
473
        return $detail;
474
    }
475
476
    /**
477
     * @param DetailModel $detail
478
     * @param DetailModel $mainDetail
479
     * @return bool
480
     */
481
    private function checkIfMainVariant(DetailModel $detail, DetailModel $mainDetail)
482
    {
483
        return $detail->getId() === $mainDetail->getId();
484
    }
485
486
    /**
487
     * @param ProductModel $model
488
     * @param Product $product
489
     */
490
    private function updateConfiguratorSetTypeFromProduct(ProductModel $model, Product $product)
491
    {
492
        if (!empty($product->variant)) {
493
            $configSet = $model->getConfiguratorSet();
494
            $configSet->setType($product->configuratorSetType);
495
        }
496
    }
497
498
    /**
499
     * @param ProductModel $model
500
     * @param Product $product
501
     */
502
    private function cleanUpConfiguratorSet(ProductModel $model, Product $product)
503
    {
504
        if (empty($product->variant) && $model->getConfiguratorSet()) {
505
            $this->manager->getConnection()->executeQuery(
506
                'UPDATE s_articles SET configurator_set_id = NULL WHERE id = ?',
507
                [$model->getId()]
508
            );
509
        }
510
    }
511
512
    /**
513
     * @param ProductModel $model
514
     */
515
    private function removeConnectImportedCategories(ProductModel $model)
516
    {
517
        /** @var \Shopware\Models\Category\Category $category */
518
        foreach ($model->getCategories() as $category) {
519
            $attribute = $category->getAttribute();
520
            if (!$attribute) {
521
                continue;
522
            }
523
524
            if ($attribute->getConnectImported()) {
525
                $model->removeCategory($category);
526
            }
527
        }
528
    }
529
530
    /**
531
     * @param DetailModel $detail
532
     * @param ProductModel $model
533
     * @return AttributeModel
534
     */
535
    private function getOrCreateAttributeModel(DetailModel $detail, ProductModel $model)
536
    {
537
        $detailAttribute = $detail->getAttribute();
538
        if (!$detailAttribute) {
539
            $detailAttribute = new AttributeModel();
540
            $detail->setAttribute($detailAttribute);
541
            $model->setAttribute($detailAttribute);
542
            $detailAttribute->setArticle($model);
543
            $detailAttribute->setArticleDetail($detail);
544
        }
545
546
        return $detailAttribute;
547
    }
548
549
    /**
550
     * Get array of update info for the known fields
551
     *
552
     * @param $model
553
     * @param $detail
554
     * @param $attribute
555
     * @param $product
556
     * @return array
557
     */
558
    public function getUpdateFields($model, $detail, $attribute, $product)
559
    {
560
        // This also defines the flags of these fields
561
        $fields = $this->helper->getUpdateFlags();
562
        $flagsByName = array_flip($fields);
563
564
        $flag = 0;
565
        $output = [];
566
        foreach ($fields as $key => $field) {
567
            // Don't handle the imageInitialImport flag
568
            if ($field == 'imageInitialImport') {
569
                continue;
570
            }
571
572
            // If this is a new product
573
            if (!$model->getId() && $field == 'image' && !$this->config->getConfig('importImagesOnFirstImport',
574
                    false)) {
575
                $output[$field] = false;
576
                $flag |= $flagsByName['imageInitialImport'];
577
                continue;
578
            }
579
580
            $updateAllowed = $this->isFieldUpdateAllowed($field, $model, $attribute);
581
            $output[$field] = $updateAllowed;
582
            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...
583
                $flag |= $key;
584
            }
585
        }
586
587
        return [$output, $flag];
588
    }
589
590
    /**
591
     * Helper method to determine if a given $fields may/must be updated.
592
     * This method will check for the model->id in order to determine, if it is a new entity. Therefore
593
     * this method cannot be used after the model in question was already flushed.
594
     *
595
     * @param $field
596
     * @param $model ProductModel
597
     * @param $attribute ConnectAttribute
598
     * @throws \RuntimeException
599
     * @return bool|null
600
     */
601
    public function isFieldUpdateAllowed($field, ProductModel $model, ConnectAttribute $attribute)
602
    {
603
        $allowed = [
604
            'ShortDescription',
605
            'LongDescription',
606
            'AdditionalDescription',
607
            'Image',
608
            'Price',
609
            'Name',
610
        ];
611
612
        // Always allow updates for new models
613
        if (!$model->getId()) {
614
            return true;
615
        }
616
617
        $field = ucfirst($field);
618
        $attributeGetter = 'getUpdate' . $field;
619
        $configName = 'overwriteProduct' . $field;
620
621
        if (!in_array($field, $allowed)) {
622
            throw new \RuntimeException("Unknown update field {$field}");
623
        }
624
625
        $attributeValue = $attribute->$attributeGetter();
626
627
628
        // If the value is 'null' or 'inherit', the behaviour will be inherited from the global configuration
629
        // Once we have a supplier based configuration, we need to take it into account here
630
        if ($attributeValue == null || $attributeValue == 'inherit') {
631
            return $this->config->getConfig($configName, true);
632
        }
633
634
        return $attributeValue == 'overwrite';
635
    }
636
637
    /**
638
     * Determine if a given field has changed
639
     *
640
     * @param $field
641
     * @param ProductModel $model
642
     * @param DetailModel $detail
643
     * @param Product $product
644
     * @return bool
645
     */
646
    public function hasFieldChanged($field, ProductModel $model, DetailModel $detail, Product $product)
647
    {
648
        switch ($field) {
649
            case 'shortDescription':
650
                return $model->getDescription() != $product->shortDescription;
651
            case 'longDescription':
652
                return $model->getDescriptionLong() != $product->longDescription;
653
            case 'additionalDescription':
654
                return $detail->getAttribute()->getConnectProductDescription() != $product->additionalDescription;
655
            case 'name':
656
                return $model->getName() != $product->title;
657
            case 'image':
658
                return count($model->getImages()) != count($product->images);
659
            case 'price':
660
                $prices = $detail->getPrices();
661
                if (empty($prices)) {
662
                    return true;
663
                }
664
                $price = $prices->first();
665
                if (!$price) {
666
                    return true;
667
                }
668
669
                return $prices->first()->getPrice() != $product->price;
670
        }
671
672
        throw new \InvalidArgumentException('Unrecognized field');
673
    }
674
675
    /**
676
     * @param array $updateFields
677
     * @param ProductModel $model
678
     * @param AttributeModel $detailAttribute
679
     * @param Product $product
680
     */
681
    private function setPropertiesForNewProducts(array $updateFields, ProductModel $model, AttributeModel $detailAttribute, Product $product)
682
    {
683
        /*
684
         * Make sure, that the following properties are set for
685
         * - new products
686
         * - products that have been configured to receive these updates
687
         */
688
        if ($updateFields['name']) {
689
            $model->setName($product->title);
690
        }
691
        if ($updateFields['shortDescription']) {
692
            $model->setDescription($product->shortDescription);
693
        }
694
        if ($updateFields['longDescription']) {
695
            $model->setDescriptionLong($product->longDescription);
696
        }
697
698
        if ($updateFields['additionalDescription']) {
699
            $detailAttribute->setConnectProductDescription($product->additionalDescription);
700
        }
701
702
        if ($product->vat !== null) {
703
            $repo = $this->manager->getRepository('Shopware\Models\Tax\Tax');
704
            $tax = round($product->vat * 100, 2);
705
            /** @var \Shopware\Models\Tax\Tax $tax */
706
            $tax = $repo->findOneBy(['tax' => $tax]);
707
            $model->setTax($tax);
708
        }
709
710
        if ($product->vendor !== null) {
711
            $repo = $this->manager->getRepository('Shopware\Models\Article\Supplier');
712
            $supplier = $repo->findOneBy(['name' => $product->vendor]);
713
            if ($supplier === null) {
714
                $supplier = $this->createSupplier($product->vendor);
715
            }
716
            $model->setSupplier($supplier);
717
        }
718
    }
719
720
    /**
721
     * @param $vendor
722
     * @return Supplier
723
     */
724
    private function createSupplier($vendor)
725
    {
726
        $supplier = new Supplier();
727
728
        if (is_array($vendor)) {
729
            $supplier->setName($vendor['name']);
730
            $supplier->setDescription($vendor['description']);
731
            if (array_key_exists('url', $vendor) && $vendor['url']) {
732
                $supplier->setLink($vendor['url']);
733
            }
734
735
            $supplier->setMetaTitle($vendor['page_title']);
736
737
            if (array_key_exists('logo_url', $vendor) && $vendor['logo_url']) {
738
                $this->imageImport->importImageForSupplier($vendor['logo_url'], $supplier);
739
            }
740
        } else {
741
            $supplier->setName($vendor);
742
        }
743
744
        //sets supplier attributes
745
        $attr = new \Shopware\Models\Attribute\ArticleSupplier();
746
        $attr->setConnectIsRemote(true);
747
748
        $supplier->setAttribute($attr);
749
750
        return $supplier;
751
    }
752
753
    /**
754
     * @param ProductModel $article
755
     * @param Product $product
756
     */
757
    private function applyProductProperties(ProductModel $article, Product $product)
758
    {
759
        if (empty($product->properties)) {
760
            return;
761
        }
762
763
        /** @var Property $firstProperty */
764
        $firstProperty = reset($product->properties);
765
        $groupRepo = $this->manager->getRepository(PropertyGroup::class);
766
        $group = $groupRepo->findOneBy(['name' => $firstProperty->groupName]);
767
768
        if (!$group) {
769
            $group = $this->createPropertyGroup($firstProperty);
770
        }
771
772
        $propertyValues = $article->getPropertyValues();
773
        $propertyValues->clear();
774
        $this->manager->persist($article);
775
        $this->manager->flush();
776
777
        $article->setPropertyGroup($group);
778
779
        $optionRepo = $this->manager->getRepository(PropertyOption::class);
780
        $valueRepo = $this->manager->getRepository(PropertyValue::class);
781
782
        foreach ($product->properties as $property) {
783
            $option = $optionRepo->findOneBy(['name' => $property->option]);
784
            $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...
785
            if (!$option) {
786
                $option = new PropertyOption();
787
                $option->setName($property->option);
788
                $option->setFilterable($property->filterable);
789
790
                $attribute = new \Shopware\Models\Attribute\PropertyOption();
791
                $attribute->setPropertyOption($option);
792
                $attribute->setConnectIsRemote(true);
793
                $option->setAttribute($attribute);
794
795
                $this->manager->persist($option);
796
                $this->manager->flush($option);
797
            }
798
799
            if (!$optionExists || !$value = $valueRepo->findOneBy(['option' => $option, 'value' => $property->value])) {
800
                $value = new PropertyValue($option, $property->value);
801
                $value->setPosition($property->valuePosition);
802
803
                $attribute = new \Shopware\Models\Attribute\PropertyValue();
804
                $attribute->setPropertyValue($value);
805
                $attribute->setConnectIsRemote(true);
806
                $value->setAttribute($attribute);
807
808
                $this->manager->persist($value);
809
            }
810
811
            if (!$propertyValues->contains($value)) {
812
                //add only new values
813
                $propertyValues->add($value);
814
            }
815
816
            $filters = [
817
                ['property' => 'options.name', 'expression' => '=', 'value' => $property->option],
818
                ['property' => 'groups.name', 'expression' => '=', 'value' => $property->groupName],
819
            ];
820
821
            $query = $groupRepo->getPropertyRelationQuery($filters, null, 1, 0);
822
            $relation = $query->getOneOrNullResult();
823
824
            if (!$relation) {
825
                $group->addOption($option);
826
                $this->manager->persist($group);
827
                $this->manager->flush($group);
828
            }
829
        }
830
831
        $article->setPropertyValues($propertyValues);
832
833
        $this->manager->persist($article);
834
        $this->manager->flush();
835
    }
836
837
    /**
838
     * Read product attributes mapping and set to shopware attribute model
839
     *
840
     * @param AttributeModel $detailAttribute
841
     * @param Product $product
842
     * @return AttributeModel
843
     */
844
    private function applyMarketplaceAttributes(AttributeModel $detailAttribute, Product $product)
845
    {
846
        $detailAttribute->setConnectReference($product->sourceId);
847
        $detailAttribute->setConnectArticleShipping($product->shipping);
848
        //todo@sb: check if connectAttribute matches position of the marketplace attribute
849
        array_walk($product->attributes, function ($value, $key) use ($detailAttribute) {
850
            $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...
851
            if (strlen($shopwareAttribute) > 0) {
852
                $setter = 'set' . ucfirst($shopwareAttribute);
853
                $detailAttribute->$setter($value);
854
            }
855
        });
856
857
        return $detailAttribute;
858
    }
859
860
    /**
861
     * @param  ConnectAttribute $connectAttribute
862
     * @param Product $product
863
     */
864
    private function setConnectAttributesFromProduct(ConnectAttribute $connectAttribute, Product $product)
865
    {
866
        $connectAttribute->setShopId($product->shopId);
867
        $connectAttribute->setSourceId($product->sourceId);
868
        $connectAttribute->setExportStatus(null);
869
        $connectAttribute->setPurchasePrice($product->purchasePrice);
870
        $connectAttribute->setFixedPrice($product->fixedPrice);
871
        $connectAttribute->setStream($product->stream);
872
873
        // store purchasePriceHash and offerValidUntil
874
        $connectAttribute->setPurchasePriceHash($product->purchasePriceHash);
875
        $connectAttribute->setOfferValidUntil($product->offerValidUntil);
876
    }
877
878
    /**
879
     * @param DetailModel $detail
880
     * @param Product $product
881
     */
882
    private function updateDetailFromProduct(DetailModel $detail, Product $product)
883
    {
884
        $detail->setInStock($product->availability);
885
        $detail->setEan($product->ean);
886
        $detail->setShippingTime($product->deliveryWorkDays);
887
        $releaseDate = new \DateTime();
888
        $releaseDate->setTimestamp($product->deliveryDate);
889
        $detail->setReleaseDate($releaseDate);
890
        $detail->setMinPurchase($product->minPurchaseQuantity);
891
    }
892
893
    /**
894
     * @param DetailModel $detail
895
     * @param Product $product
896
     * @param $detailAttribute
897
     */
898
    private function detailSetUnit(DetailModel $detail, Product $product, $detailAttribute)
899
    {
900
        // if connect product has unit
901
        // find local unit with units mapping
902
        // and add to detail model
903
        if (array_key_exists('unit', $product->attributes) && $product->attributes['unit']) {
904
            $detailAttribute->setConnectRemoteUnit($product->attributes['unit']);
905
            if ($this->config->getConfig($product->attributes['unit']) == null) {
906
                $this->config->setConfig($product->attributes['unit'], '', null, 'units');
907
            }
908
909
            /** @var \ShopwarePlugins\Connect\Components\Utils\UnitMapper $unitMapper */
910
            $unitMapper = new UnitMapper($this->config, $this->manager);
911
912
            $shopwareUnit = $unitMapper->getShopwareUnit($product->attributes['unit']);
913
914
            /** @var \Shopware\Models\Article\Unit $unit */
915
            $unit = $this->helper->getUnit($shopwareUnit);
916
            $detail->setUnit($unit);
917
            $detail->setPurchaseUnit($product->attributes['quantity']);
918
            $detail->setReferenceUnit($product->attributes['ref_quantity']);
919
        } else {
920
            $detail->setUnit(null);
921
            $detail->setPurchaseUnit(null);
922
            $detail->setReferenceUnit(null);
923
        }
924
    }
925
926
    /**
927
     * @param DetailModel $detail
928
     * @param Product $product
929
     */
930
    private function detailSetAttributes(DetailModel $detail, Product $product)
931
    {
932
        // set dimension
933
        if (array_key_exists('dimension', $product->attributes) && $product->attributes['dimension']) {
934
            $dimension = explode('x', $product->attributes['dimension']);
935
            $detail->setLen($dimension[0]);
936
            $detail->setWidth($dimension[1]);
937
            $detail->setHeight($dimension[2]);
938
        } else {
939
            $detail->setLen(null);
940
            $detail->setWidth(null);
941
            $detail->setHeight(null);
942
        }
943
944
        // set weight
945
        if (array_key_exists('weight', $product->attributes) && $product->attributes['weight']) {
946
            $detail->setWeight($product->attributes['weight']);
947
        }
948
949
        //set package unit
950
        if (array_key_exists(Product::ATTRIBUTE_PACKAGEUNIT, $product->attributes)) {
951
            $detail->setPackUnit($product->attributes[Product::ATTRIBUTE_PACKAGEUNIT]);
952
        }
953
954
        //set basic unit
955
        if (array_key_exists(Product::ATTRIBUTE_BASICUNIT, $product->attributes)) {
956
            $detail->setMinPurchase($product->attributes[Product::ATTRIBUTE_BASICUNIT]);
957
        }
958
959
        //set manufacturer no.
960
        if (array_key_exists(Product::ATTRIBUTE_MANUFACTURERNUMBER, $product->attributes)) {
961
            $detail->setSupplierNumber($product->attributes[Product::ATTRIBUTE_MANUFACTURERNUMBER]);
962
        }
963
    }
964
965
    /**
966
     * @param ConnectAttribute $connectAttribute
967
     * @param Product $product
968
     */
969
    private function connectAttributeSetLastUpdate(ConnectAttribute $connectAttribute, Product $product)
970
    {
971
        // Whenever a product is updated, store a json encoded list of all fields that are updated optionally
972
        // This way a customer will be able to apply the most recent changes any time later
973
        $connectAttribute->setLastUpdate(json_encode([
974
            'shortDescription' => $product->shortDescription,
975
            'longDescription' => $product->longDescription,
976
            'additionalDescription' => $product->additionalDescription,
977
            'purchasePrice' => $product->purchasePrice,
978
            'image' => $product->images,
979
            'variantImages' => $product->variantImages,
980
            'price' => $product->price * ($product->vat + 1),
981
            'name' => $product->title,
982
            'vat' => $product->vat
983
        ]));
984
    }
985
986
    /**
987
     * @param ProductModel $model
988
     * @param array $categories
989
     */
990
    private function categoryDenormalization(ProductModel $model, array $categories)
991
    {
992
        $this->categoryDenormalization->disableTransactions();
993
        foreach ($categories as $category) {
994
            $this->categoryDenormalization->addAssignment($model->getId(), $category);
995
            $this->manager->getConnection()->executeQuery(
996
                'INSERT IGNORE INTO `s_articles_categories` (`articleID`, `categoryID`) VALUES (?,?)',
997
                [$model->getId(), $category]
998
            );
999
        }
1000
        $this->categoryDenormalization->enableTransactions();
1001
    }
1002
1003
    /**
1004
     * @param Product $product
1005
     * @return ProductStream
1006
     */
1007
    private function getOrCreateStream(Product $product)
1008
    {
1009
        /** @var ProductStreamRepository $repo */
1010
        $repo = $this->manager->getRepository(ProductStreamAttribute::class);
1011
        $stream = $repo->findConnectByName($product->stream);
1012
1013
        if (!$stream) {
1014
            $stream = new ProductStream();
1015
            $stream->setName($product->stream);
1016
            $stream->setType(ProductStreamService::STATIC_STREAM);
1017
            $stream->setSorting(json_encode(
1018
                [ReleaseDateSorting::class => ['direction' => 'desc']]
1019
            ));
1020
1021
            //add attributes
1022
            $attribute = new \Shopware\Models\Attribute\ProductStream();
1023
            $attribute->setProductStream($stream);
1024
            $attribute->setConnectIsRemote(true);
1025
            $stream->setAttribute($attribute);
1026
1027
            $this->manager->persist($attribute);
1028
            $this->manager->persist($stream);
1029
            $this->manager->flush();
1030
        }
1031
1032
        return $stream;
1033
    }
1034
1035
    /**
1036
     * @param ProductStream $stream
1037
     * @param ProductModel $article
1038
     * @throws \Doctrine\DBAL\DBALException
1039
     */
1040
    private function addProductToStream(ProductStream $stream, ProductModel $article)
1041
    {
1042
        $conn = $this->manager->getConnection();
1043
        $sql = 'INSERT INTO `s_product_streams_selection` (`stream_id`, `article_id`)
1044
                VALUES (:streamId, :articleId)
1045
                ON DUPLICATE KEY UPDATE stream_id = :streamId, article_id = :articleId';
1046
        $stmt = $conn->prepare($sql);
1047
        $stmt->execute([':streamId' => $stream->getId(), ':articleId' => $article->getId()]);
1048
    }
1049
1050
    /**
1051
     * Set detail purchase price with plain SQL
1052
     * Entity usage throws exception when error handlers are disabled
1053
     *
1054
     * @param ProductModel $article
1055
     * @param DetailModel $detail
1056
     * @param Product $product
1057
     * @throws \Doctrine\DBAL\DBALException
1058
     */
1059
    private function setPrice(ProductModel $article, DetailModel $detail, Product $product)
1060
    {
1061
        // set price via plain SQL because shopware throws exception
1062
        // undefined index: key when error handler is disabled
1063
        $customerGroup = $this->helper->getDefaultCustomerGroup();
1064
1065
        if (!empty($product->priceRanges)) {
1066
            $this->setPriceRange($article, $detail, $product->priceRanges, $customerGroup);
1067
1068
            return;
1069
        }
1070
1071
        $id = $this->manager->getConnection()->fetchColumn(
1072
            'SELECT id FROM `s_articles_prices`
1073
              WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?',
1074
            [$customerGroup->getKey(), 1, 'beliebig', $article->getId(), $detail->getId()]
1075
        );
1076
1077
        // todo@sb: test update prices
1078
        if ($id > 0) {
1079
            $this->manager->getConnection()->executeQuery(
1080
                'UPDATE `s_articles_prices` SET `price` = ?, `baseprice` = ? WHERE `id` = ?',
1081
                [$product->price, $product->purchasePrice, $id]
1082
            );
1083
        } else {
1084
            $this->manager->getConnection()->executeQuery(
1085
                'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`, `baseprice`)
1086
              VALUES (?, 1, "beliebig", ?, ?, ?, ?);',
1087
                [
1088
                    $customerGroup->getKey(),
1089
                    $article->getId(),
1090
                    $detail->getId(),
1091
                    $product->price,
1092
                    $product->purchasePrice
1093
                ]
1094
            );
1095
        }
1096
    }
1097
1098
    /**
1099
     * @param ProductModel $article
1100
     * @param DetailModel $detail
1101
     * @param array $priceRanges
1102
     * @param Group $group
1103
     * @throws \Doctrine\DBAL\ConnectionException
1104
     * @throws \Exception
1105
     */
1106
    private function setPriceRange(ProductModel $article, DetailModel $detail, array $priceRanges, Group $group)
1107
    {
1108
        $this->manager->getConnection()->beginTransaction();
1109
1110
        try {
1111
            // We always delete the prices,
1112
            // because we can not know which record is update
1113
            $this->manager->getConnection()->executeQuery(
1114
                'DELETE FROM `s_articles_prices` WHERE `articleID` = ? AND `articledetailsID` = ?',
1115
                [$article->getId(), $detail->getId()]
1116
            );
1117
1118
            /** @var PriceRange $priceRange */
1119
            foreach ($priceRanges as $priceRange) {
1120
                $priceTo = $priceRange->to == PriceRange::ANY ? 'beliebig' : $priceRange->to;
1121
1122
                //todo: maybe batch insert if possible?
1123
                $this->manager->getConnection()->executeQuery(
1124
                    'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`)
1125
                      VALUES (?, ?, ?, ?, ?, ?);',
1126
                    [
1127
                        $group->getKey(),
1128
                        $priceRange->from,
1129
                        $priceTo,
1130
                        $article->getId(),
1131
                        $detail->getId(),
1132
                        $priceRange->price
1133
                    ]
1134
                );
1135
            }
1136
            $this->manager->getConnection()->commit();
1137
        } catch (\Exception $e) {
1138
            $this->manager->getConnection()->rollBack();
1139
            throw new \Exception($e->getMessage());
1140
        }
1141
    }
1142
1143
    /**
1144
     * Set detail purchase price with plain SQL
1145
     * Entity usage throws exception when error handlers are disabled
1146
     *
1147
     * @param DetailModel $detail
1148
     * @param float $purchasePrice
1149
     * @param Group $defaultGroup
1150
     * @throws \Doctrine\DBAL\DBALException
1151
     */
1152
    private function setPurchasePrice(DetailModel $detail, $purchasePrice, Group $defaultGroup)
1153
    {
1154
        if (method_exists($detail, 'setPurchasePrice')) {
1155
            $this->manager->getConnection()->executeQuery(
1156
                'UPDATE `s_articles_details` SET `purchaseprice` = ? WHERE `id` = ?',
1157
                [$purchasePrice, $detail->getId()]
1158
            );
1159
        } else {
1160
            $id = $this->manager->getConnection()->fetchColumn(
1161
                'SELECT id FROM `s_articles_prices`
1162
              WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?',
1163
                [$defaultGroup->getKey(), 1, 'beliebig', $detail->getArticleId(), $detail->getId()]
1164
            );
1165
1166
            if ($id > 0) {
1167
                $this->manager->getConnection()->executeQuery(
1168
                    'UPDATE `s_articles_prices` SET `baseprice` = ? WHERE `id` = ?',
1169
                    [$purchasePrice, $id]
1170
                );
1171
            } else {
1172
                $this->manager->getConnection()->executeQuery(
1173
                    'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `baseprice`)
1174
              VALUES (?, 1, "beliebig", ?, ?, ?);',
1175
                    [$defaultGroup->getKey(), $detail->getArticleId(), $detail->getId(), $purchasePrice]
1176
                );
1177
            }
1178
        }
1179
    }
1180
1181
    /**
1182
     * Adds translation record for given article
1183
     *
1184
     * @param ProductModel $article
1185
     * @param Product $sdkProduct
1186
     */
1187
    private function addArticleTranslations(ProductModel $article, Product $sdkProduct)
1188
    {
1189
        /** @var \Shopware\Connect\Struct\Translation $translation */
1190
        foreach ($sdkProduct->translations as $key => $translation) {
1191
            /** @var \Shopware\Models\Shop\Locale $locale */
1192
            $locale = $this->getLocaleRepository()->findOneBy(['locale' => LocaleMapper::getShopwareLocale($key)]);
1193
            /** @var \Shopware\Models\Shop\Shop $shop */
1194
            $shop = $this->getShopRepository()->findOneBy(['locale' => $locale]);
1195
            if (!$shop) {
1196
                continue;
1197
            }
1198
1199
            $this->productTranslationsGateway->addArticleTranslation($translation, $article->getId(), $shop->getId());
1200
        }
1201
    }
1202
1203
    /**
1204
     * dsadsa
1205
     * @return \Shopware\Components\Model\ModelRepository
1206
     */
1207
    private function getLocaleRepository()
1208
    {
1209
        if (!$this->localeRepository) {
1210
            $this->localeRepository = $this->manager->getRepository('Shopware\Models\Shop\Locale');
1211
        }
1212
1213
        return $this->localeRepository;
1214
    }
1215
1216
    private function getShopRepository()
1217
    {
1218
        if (!$this->shopRepository) {
1219
            $this->shopRepository = $this->manager->getRepository('Shopware\Models\Shop\Shop');
1220
        }
1221
1222
        return $this->shopRepository;
1223
    }
1224
1225
    /**
1226
     * Delete product or product variant with given shopId and sourceId.
1227
     *
1228
     * Only the combination of both identifies a product uniquely. Do NOT
1229
     * delete products just by their sourceId.
1230
     *
1231
     * You might receive delete requests for products, which are not available
1232
     * in your shop. Just ignore them.
1233
     *
1234
     * @param string $shopId
1235
     * @param string $sourceId
1236
     * @return void
1237
     */
1238
    public function delete($shopId, $sourceId)
1239
    {
1240
        $detail = $this->helper->getArticleDetailModelByProduct(new Product([
1241
            'shopId' => $shopId,
1242
            'sourceId' => $sourceId,
1243
        ]));
1244
        if ($detail === null) {
1245
            return;
1246
        }
1247
1248
        $this->deleteDetail($detail);
1249
    }
1250
1251
    public function update($shopId, $sourceId, ProductUpdate $product)
1252
    {
1253
        // find article detail id
1254
        $articleDetailId = $this->manager->getConnection()->fetchColumn(
1255
            'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?',
1256
            [$sourceId, $shopId]
1257
        );
1258
1259
        $this->eventManager->notify(
1260
            'Connect_Merchant_Update_GeneralProductInformation',
1261
            [
1262
                'subject' => $this,
1263
                'shopId' => $shopId,
1264
                'sourceId' => $sourceId,
1265
                'articleDetailId' => $articleDetailId
1266
            ]
1267
        );
1268
1269
        // update purchasePriceHash, offerValidUntil and purchasePrice in connect attribute
1270
        $this->manager->getConnection()->executeUpdate(
1271
            'UPDATE s_plugin_connect_items SET purchase_price_hash = ?, offer_valid_until = ?, purchase_price = ?
1272
            WHERE source_id = ? AND shop_id = ?',
1273
            [
1274
                $product->purchasePriceHash,
1275
                $product->offerValidUntil,
1276
                $product->purchasePrice,
1277
                $sourceId,
1278
                $shopId,
1279
            ]
1280
        );
1281
1282
        // update stock in article detail
1283
        // update prices
1284
        // if purchase price is stored in article detail
1285
        // update it together with stock
1286
        // since shopware 5.2
1287
        if (method_exists('Shopware\Models\Article\Detail', 'getPurchasePrice')) {
1288
            $this->manager->getConnection()->executeUpdate(
1289
                'UPDATE s_articles_details SET instock = ?, purchaseprice = ? WHERE id = ?',
1290
                [$product->availability, $product->purchasePrice, $articleDetailId]
1291
            );
1292
        } else {
1293
            $this->manager->getConnection()->executeUpdate(
1294
                'UPDATE s_articles_details SET instock = ? WHERE id = ?',
1295
                [$product->availability, $articleDetailId]
1296
            );
1297
        }
1298
        $this->manager->getConnection()->executeUpdate(
1299
            "UPDATE s_articles_prices SET price = ?, baseprice = ? WHERE articledetailsID = ? AND pricegroup = 'EK'",
1300
            [$product->price, $product->purchasePrice, $articleDetailId]
1301
        );
1302
    }
1303
1304
    public function changeAvailability($shopId, $sourceId, $availability)
1305
    {
1306
        // find article detail id
1307
        $articleDetailId = $this->manager->getConnection()->fetchColumn(
1308
            'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?',
1309
            [$sourceId, $shopId]
1310
        );
1311
1312
        $this->eventManager->notify(
1313
            'Connect_Merchant_Update_GeneralProductInformation',
1314
            [
1315
                'subject' => $this,
1316
                'shopId' => $shopId,
1317
                'sourceId' => $sourceId,
1318
                'articleDetailId' => $articleDetailId
1319
            ]
1320
        );
1321
1322
        // update stock in article detail
1323
        $this->manager->getConnection()->executeUpdate(
1324
            'UPDATE s_articles_details SET instock = ? WHERE id = ?',
1325
            [$availability, $articleDetailId]
1326
        );
1327
    }
1328
1329
    /**
1330
     * @inheritDoc
1331
     */
1332
    public function makeMainVariant($shopId, $sourceId, $groupId)
1333
    {
1334
        //find article detail which should be selected as main one
1335
        $newMainDetail = $this->helper->getConnectArticleDetailModel($sourceId, $shopId);
1336
        if (!$newMainDetail) {
1337
            return;
1338
        }
1339
1340
        /** @var \Shopware\Models\Article\Article $article */
1341
        $article = $newMainDetail->getArticle();
1342
1343
        $this->eventManager->notify(
1344
            'Connect_Merchant_Update_ProductMainVariant_Before',
1345
            [
1346
                'subject' => $this,
1347
                'shopId' => $shopId,
1348
                'sourceId' => $sourceId,
1349
                'articleId' => $article->getId(),
1350
                'articleDetailId' => $newMainDetail->getId()
1351
            ]
1352
        );
1353
1354
        // replace current main detail with new one
1355
        $currentMainDetail = $article->getMainDetail();
1356
        $currentMainDetail->setKind(2);
1357
        $newMainDetail->setKind(1);
1358
        $article->setMainDetail($newMainDetail);
1359
1360
        $this->manager->persist($newMainDetail);
1361
        $this->manager->persist($currentMainDetail);
1362
        $this->manager->persist($article);
1363
        $this->manager->flush();
1364
    }
1365
1366
    /**
1367
     * Updates the status of an Order
1368
     *
1369
     * @param string $localOrderId
1370
     * @param string $orderStatus
1371
     * @param string $trackingNumber
1372
     * @return void
1373
     */
1374
    public function updateOrderStatus($localOrderId, $orderStatus, $trackingNumber)
1375
    {
1376
        if ($this->config->getConfig('updateOrderStatus') == 1) {
1377
            $this->updateDeliveryStatus($localOrderId, $orderStatus);
1378
        }
1379
1380
        if ($trackingNumber) {
1381
            $this->updateTrackingNumber($localOrderId, $trackingNumber);
1382
        }
1383
    }
1384
1385
    /**
1386
     * @param string $localOrderId
1387
     * @param string $orderStatus
1388
     */
1389
    private function updateDeliveryStatus($localOrderId, $orderStatus)
1390
    {
1391
        $status = false;
1392
        if ($orderStatus === OrderStatus::STATE_IN_PROCESS) {
1393
            $status = Status::ORDER_STATE_PARTIALLY_DELIVERED;
1394
        } elseif ($orderStatus === OrderStatus::STATE_DELIVERED) {
1395
            $status = Status::ORDER_STATE_COMPLETELY_DELIVERED;
1396
        }
1397
1398
        if ($status) {
1399
            $this->manager->getConnection()->executeQuery(
1400
                'UPDATE s_order 
1401
                SET status = :orderStatus
1402
                WHERE ordernumber = :orderNumber',
1403
                [
1404
                    ':orderStatus' => $status,
1405
                    ':orderNumber' => $localOrderId
1406
                ]
1407
            );
1408
        }
1409
    }
1410
1411
    /**
1412
     * @param string $localOrderId
1413
     * @param string $trackingNumber
1414
     */
1415
    private function updateTrackingNumber($localOrderId, $trackingNumber)
1416
    {
1417
        $currentTrackingCode = $this->manager->getConnection()->fetchColumn(
1418
            'SELECT trackingcode
1419
            FROM s_order
1420
            WHERE ordernumber = :orderNumber',
1421
            [
1422
                ':orderNumber' => $localOrderId
1423
            ]
1424
        );
1425
1426
        if (!$currentTrackingCode) {
1427
            $newTracking = $trackingNumber;
1428
        } else {
1429
            $newTracking = $this->combineTrackingNumbers($trackingNumber, $currentTrackingCode);
1430
        }
1431
1432
        $this->manager->getConnection()->executeQuery(
1433
            'UPDATE s_order 
1434
            SET trackingcode = :trackingCode
1435
            WHERE ordernumber = :orderNumber',
1436
            [
1437
                ':trackingCode' => $newTracking,
1438
                ':orderNumber' => $localOrderId
1439
            ]
1440
        );
1441
    }
1442
1443
    /**
1444
     * @param string $newTrackingCode
1445
     * @param string $currentTrackingCode
1446
     * @return string
1447
     */
1448
    private function combineTrackingNumbers($newTrackingCode, $currentTrackingCode)
1449
    {
1450
        $currentTrackingCodes = $this->getTrackingNumberAsArray($currentTrackingCode);
1451
        $newTrackingCodes = $this->getTrackingNumberAsArray($newTrackingCode);
1452
        $newTrackingCodes = array_unique(array_merge($currentTrackingCodes, $newTrackingCodes));
1453
        $newTracking = implode(',', $newTrackingCodes);
1454
1455
        return $newTracking;
1456
    }
1457
1458
    /**
1459
     * @param string $trackingCode
1460
     * @return string[]
1461
     */
1462
    private function getTrackingNumberAsArray($trackingCode)
1463
    {
1464
        if (strpos($trackingCode, ',') !== false) {
1465
            return explode(',', $trackingCode);
1466
        }
1467
1468
        return [$trackingCode];
1469
    }
1470
1471
    /**
1472
     * @param Product $product
1473
     * @param ProductModel $model
1474
     */
1475
    private function saveVat(Product $product, ProductModel $model)
1476
    {
1477
        if ($product->vat !== null) {
1478
            $repo = $this->manager->getRepository(Tax::class);
1479
            $taxRate = round($product->vat * 100, 2);
1480
            /** @var \Shopware\Models\Tax\Tax $tax */
1481
            $tax = $repo->findOneBy(['tax' => $taxRate]);
1482
            if (!$tax) {
1483
                $tax = new Tax();
1484
                $tax->setTax($taxRate);
1485
                //this is to get rid of zeroes behind the decimal point
1486
                $name = strval(round($taxRate, 2)) . '%';
1487
                $tax->setName($name);
1488
                $this->manager->persist($tax);
1489
            }
1490
            $model->setTax($tax);
1491
        }
1492
    }
1493
1494
    /**
1495
     * @param Property $property
1496
     * @return PropertyGroup
1497
     */
1498
    private function createPropertyGroup(Property $property)
1499
    {
1500
        $group = new PropertyGroup();
1501
        $group->setName($property->groupName);
1502
        $group->setComparable($property->comparable);
1503
        $group->setSortMode($property->sortMode);
1504
        $group->setPosition($property->groupPosition);
1505
1506
        $attribute = new \Shopware\Models\Attribute\PropertyGroup();
1507
        $attribute->setPropertyGroup($group);
1508
        $attribute->setConnectIsRemote(true);
1509
        $group->setAttribute($attribute);
1510
1511
        $this->manager->persist($attribute);
1512
        $this->manager->persist($group);
1513
        $this->manager->flush();
1514
1515
        return $group;
1516
    }
1517
}
1518