Completed
Push — master ( 310bdb...aee174 )
by Jonas
05:01
created

ProductToShop::getOrCreateAttributeModel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $product->related of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1545
            $this->storeCrossSellingInformationOwningSide($articleId, $product);
1546
        }
1547
    }
1548
1549
    /**
1550
     * @param int $articleId
1551
     * @param Product $product
1552
     */
1553
    private function storeCrossSellingInformationOwningSide($articleId, $product)
1554
    {
1555
        foreach ($product->related as $relatedId) {
1556
            $this->insertNewRelations($articleId, $product->shopId, $relatedId, self::RELATION_TYPE_RELATED);
1557
        }
1558
1559
        foreach ($product->similar as $similarId) {
1560
            $this->insertNewRelations($articleId, $product->shopId, $similarId, self::RELATION_TYPE_SIMILAR);
1561
        }
1562
    }
1563
1564
    /**
1565
     * @param int $articleId
1566
     * @param int $shopId
1567
     * @param int $relatedId
1568
     * @param string $relationType
1569
     */
1570
    private function insertNewRelations($articleId, $shopId, $relatedId, $relationType)
1571
    {
1572
        $inserted = false;
1573
        try {
1574
            $this->manager->getConnection()->executeQuery(
1575
                'INSERT INTO s_plugin_connect_article_relations (article_id, shop_id, related_article_local_id, relationship_type) VALUES (?, ?, ?, ?)',
1576
                [$articleId, $shopId, $relatedId, $relationType]
1577
            );
1578
            $inserted = true;
1579
        } catch (\Doctrine\DBAL\DBALException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
1580
            // No problems here. Just means that the row already existed.
1581
        }
1582
1583
        //outside of try catch because we don't want to catch exceptions -> this method should not throw any
1584
        if ($inserted) {
1585
            $relatedLocalId = $this->manager->getConnection()->fetchColumn(
1586
                'SELECT article_id FROM s_plugin_connect_items WHERE shop_id = ? AND source_id = ?',
1587
                [$shopId, $relatedId]
1588
            );
1589
            if ($relatedLocalId) {
1590
                $this->manager->getConnection()->executeQuery(
1591
                    "INSERT IGNORE INTO s_articles_$relationType (articleID, relatedarticle) VALUES (?, ?)",
1592
                    [$articleId, $relatedLocalId]
1593
                );
1594
            }
1595
        }
1596
    }
1597
1598
    /**
1599
     * @param int $articleId
1600
     * @param string $sourceId
1601
     * @param int $shopId
1602
     */
1603
    private function storeCrossSellingInformationInverseSide($articleId, $sourceId, $shopId)
1604
    {
1605
        $relatedArticles = $this->manager->getConnection()->fetchAll(
1606
            'SELECT article_id, relationship_type FROM s_plugin_connect_article_relations WHERE shop_id = ? AND related_article_local_id = ?',
1607
            [$shopId, $sourceId]
1608
        );
1609
1610
        foreach ($relatedArticles as $relatedArticle) {
1611
            $relationType = $relatedArticle['relationship_type'];
1612
            $this->manager->getConnection()->executeQuery(
1613
                "INSERT IGNORE INTO s_articles_$relationType (articleID, relatedarticle) VALUES (?, ?)",
1614
                [$relatedArticle['article_id'], $articleId]
1615
            );
1616
        }
1617
    }
1618
1619
    /**
1620
     * @param $articleId
1621
     * @param $product
1622
     */
1623
    private function deleteRemovedRelations($articleId, $product)
1624
    {
1625 View Code Duplication
        if (count($product->related) > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1626
            $this->manager->getConnection()->executeQuery(
1627
                'DELETE FROM s_plugin_connect_article_relations WHERE article_id = ? AND shop_id = ? AND related_article_local_id NOT IN (?) AND relationship_type = ?',
1628
                [$articleId, $product->shopId, $product->related, self::RELATION_TYPE_RELATED],
1629
                [\PDO::PARAM_INT, \PDO::PARAM_INT, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY, \PDO::PARAM_STR]
1630
            );
1631
1632
            $oldRelatedIds = $this->manager->getConnection()->executeQuery(
1633
                'SELECT ar.id 
1634
                FROM s_articles_relationships AS ar
1635
                INNER JOIN s_plugin_connect_items AS ci ON ar.relatedarticle = ci.article_id
1636
                WHERE ar.articleID = ? AND ci.shop_id = ? AND ci.source_id NOT IN (?)',
1637
                [$articleId, $product->shopId, $product->related],
1638
                [\PDO::PARAM_INT, \PDO::PARAM_INT, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY]
1639
            )
1640
                ->fetchAll(\PDO::FETCH_COLUMN);
1641
        } else {
1642
            $this->manager->getConnection()->executeQuery(
1643
                'DELETE FROM s_plugin_connect_article_relations WHERE article_id = ? AND shop_id = ? AND relationship_type = ?',
1644
                [$articleId, $product->shopId, self::RELATION_TYPE_RELATED]
1645
            );
1646
1647
            $oldRelatedIds = $this->manager->getConnection()->executeQuery(
1648
                'SELECT ar.id 
1649
                FROM s_articles_relationships AS ar
1650
                INNER JOIN s_plugin_connect_items AS ci ON ar.relatedarticle = ci.article_id
1651
                WHERE ar.articleID = ? AND ci.shop_id = ?',
1652
                [$articleId, $product->shopId]
1653
            )
1654
                ->fetchAll(\PDO::FETCH_COLUMN);
1655
        }
1656
1657
        $this->manager->getConnection()->executeQuery(
1658
            'DELETE FROM s_articles_relationships WHERE id IN (?)',
1659
            [$oldRelatedIds],
1660
            [\Doctrine\DBAL\Connection::PARAM_INT_ARRAY]
1661
        );
1662
1663 View Code Duplication
        if (count($product->similar) > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1664
            $this->manager->getConnection()->executeQuery(
1665
                'DELETE FROM s_plugin_connect_article_relations WHERE article_id = ? AND shop_id = ? AND related_article_local_id NOT IN (?) AND relationship_type = ?',
1666
                [$articleId, $product->shopId, $product->similar, self::RELATION_TYPE_SIMILAR],
1667
                [\PDO::PARAM_INT, \PDO::PARAM_INT, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY, \PDO::PARAM_STR]
1668
            );
1669
1670
            $oldSimilarIds = $this->manager->getConnection()->executeQuery(
1671
                'SELECT ar.id 
1672
                FROM s_articles_similar AS ar
1673
                INNER JOIN s_plugin_connect_items AS ci ON ar.relatedarticle = ci.article_id
1674
                WHERE ar.articleID = ? AND ci.shop_id = ? AND ci.source_id NOT IN (?)',
1675
                [$articleId, $product->shopId, $product->similar],
1676
                [\PDO::PARAM_INT, \PDO::PARAM_INT, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY]
1677
            )
1678
                ->fetchAll(\PDO::FETCH_COLUMN);
1679
        } else {
1680
            $this->manager->getConnection()->executeQuery(
1681
                'DELETE FROM s_plugin_connect_article_relations WHERE article_id = ? AND shop_id = ? AND relationship_type = ?',
1682
                [$articleId, $product->shopId, self::RELATION_TYPE_SIMILAR]
1683
            );
1684
1685
            $oldSimilarIds = $this->manager->getConnection()->executeQuery(
1686
                'SELECT ar.id 
1687
                FROM s_articles_similar AS ar
1688
                INNER JOIN s_plugin_connect_items AS ci ON ar.relatedarticle = ci.article_id
1689
                WHERE ar.articleID = ? AND ci.shop_id = ?',
1690
                [$articleId, $product->shopId]
1691
            )
1692
                ->fetchAll(\PDO::FETCH_COLUMN);
1693
        }
1694
1695
        $this->manager->getConnection()->executeQuery(
1696
            'DELETE FROM s_articles_similar WHERE id IN (?)',
1697
            [$oldSimilarIds],
1698
            [\Doctrine\DBAL\Connection::PARAM_INT_ARRAY]
1699
        );
1700
    }
1701
1702
    /**
1703
     * @param Property $property
1704
     * @return PropertyGroup
1705
     */
1706
    private function createPropertyGroup(Property $property)
1707
    {
1708
        $group = new PropertyGroup();
1709
        $group->setName($property->groupName);
1710
        $group->setComparable($property->comparable);
1711
        $group->setSortMode($property->sortMode);
1712
        $group->setPosition($property->groupPosition);
1713
1714
        $attribute = new \Shopware\Models\Attribute\PropertyGroup();
1715
        $attribute->setPropertyGroup($group);
1716
        $attribute->setConnectIsRemote(true);
1717
        $group->setAttribute($attribute);
1718
1719
        $this->manager->persist($attribute);
1720
        $this->manager->persist($group);
1721
        $this->manager->flush();
1722
1723
        return $group;
1724
    }
1725
}
1726