Completed
Pull Request — master (#378)
by Stefan
04:33
created

ProductToShop::applyProductProperties()   C

Complexity

Conditions 9
Paths 35

Size

Total Lines 92
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 92
rs 5.1048
c 0
b 0
f 0
cc 9
eloc 61
nc 35
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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