Completed
Pull Request — master (#410)
by Jonas
03:01
created

ProductToShop::deleteDetail()   C

Complexity

Conditions 8
Paths 32

Size

Total Lines 64
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 33
nc 32
nop 1
dl 0
loc 64
rs 6.8232
c 0
b 0
f 0

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