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

ProductToShop::setPriceRange()   B

Complexity

Conditions 4
Paths 10

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 36
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 20
nc 10
nop 4
1
<?php
2
/**
3
 * (c) shopware AG <[email protected]>
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace ShopwarePlugins\Connect\Components;
9
10
use Shopware\Bundle\SearchBundle\Sorting\ReleaseDateSorting;
11
use Shopware\Connect\Gateway;
12
use Shopware\Components\Model\CategoryDenormalization;
13
use Shopware\Connect\ProductToShop as ProductToShopBase;
14
use Shopware\Connect\Struct\OrderStatus;
15
use Shopware\Connect\Struct\Product;
16
use Shopware\Models\Article\Article as ProductModel;
17
use Shopware\Models\Order\Order;
18
use Shopware\Models\Order\Status;
19
use Shopware\Models\Article\Detail as DetailModel;
20
use Shopware\Models\Attribute\Article as AttributeModel;
21
use Shopware\Components\Model\ModelManager;
22
use Shopware\Connect\Struct\PriceRange;
23
use Shopware\Connect\Struct\ProductUpdate;
24
use Shopware\CustomModels\Connect\ProductStreamAttribute;
25
use Shopware\Models\Customer\Group;
26
use Shopware\Connect\Struct\Property;
27
use Shopware\Models\ProductStream\ProductStream;
28
use Shopware\Models\Property\Group as PropertyGroup;
29
use Shopware\Models\Property\Option as PropertyOption;
30
use Shopware\Models\Property\Value as PropertyValue;
31
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamRepository;
32
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamService;
33
use ShopwarePlugins\Connect\Components\Translations\LocaleMapper;
34
use ShopwarePlugins\Connect\Components\Gateway\ProductTranslationsGateway;
35
use ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway;
36
use ShopwarePlugins\Connect\Components\Utils\UnitMapper;
37
use Shopware\CustomModels\Connect\Attribute as ConnectAttribute;
38
use Shopware\Models\Article\Image;
39
use Shopware\Models\Article\Supplier;
40
use Shopware\Models\Tax\Tax;
41
42
/**
43
 * The interface for products imported *from* connect *to* the local shop
44
 *
45
 * @category  Shopware
46
 * @package   Shopware\Plugins\SwagConnect
47
 */
48
class ProductToShop implements ProductToShopBase
49
{
50
    /**
51
     * @var Helper
52
     */
53
    private $helper;
54
55
    /**
56
     * @var ModelManager
57
     */
58
    private $manager;
59
60
    /**
61
     * @var \ShopwarePlugins\Connect\Components\Config
62
     */
63
    private $config;
64
65
    /**
66
     * @var ImageImport
67
     */
68
    private $imageImport;
69
70
    /**
71
     * @var \ShopwarePlugins\Connect\Components\VariantConfigurator
72
     */
73
    private $variantConfigurator;
74
75
    /**
76
     * @var MarketplaceGateway
77
     */
78
    private $marketplaceGateway;
79
80
    /**
81
     * @var ProductTranslationsGateway
82
     */
83
    private $productTranslationsGateway;
84
85
    /**
86
     * @var \Shopware\Models\Shop\Repository
87
     */
88
    private $shopRepository;
89
90
    private $localeRepository;
91
92
    /**
93
     * @var CategoryResolver
94
     */
95
    private $categoryResolver;
96
97
    /**
98
     * @var \Shopware\Connect\Gateway
99
     */
100
    private $connectGateway;
101
102
    /**
103
     * @var \Enlight_Event_EventManager
104
     */
105
    private $eventManager;
106
107
    /**
108
     * @var CategoryDenormalization
109
     */
110
    private $categoryDenormalization;
111
112
    /**
113
     * @param Helper $helper
114
     * @param ModelManager $manager
115
     * @param ImageImport $imageImport
116
     * @param \ShopwarePlugins\Connect\Components\Config $config
117
     * @param VariantConfigurator $variantConfigurator
118
     * @param \ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway $marketplaceGateway
119
     * @param ProductTranslationsGateway $productTranslationsGateway
120
     * @param CategoryResolver $categoryResolver
121
     * @param Gateway $connectGateway
122
     * @param \Enlight_Event_EventManager $eventManager
123
     * @param CategoryDenormalization $categoryDenormalization
124
     */
125
    public function __construct(
126
        Helper $helper,
127
        ModelManager $manager,
128
        ImageImport $imageImport,
129
        Config $config,
130
        VariantConfigurator $variantConfigurator,
131
        MarketplaceGateway $marketplaceGateway,
132
        ProductTranslationsGateway $productTranslationsGateway,
133
        CategoryResolver $categoryResolver,
134
        Gateway $connectGateway,
135
        \Enlight_Event_EventManager $eventManager,
136
        CategoryDenormalization $categoryDenormalization
137
    ) {
138
        $this->helper = $helper;
139
        $this->manager = $manager;
140
        $this->config = $config;
141
        $this->imageImport = $imageImport;
142
        $this->variantConfigurator = $variantConfigurator;
143
        $this->marketplaceGateway = $marketplaceGateway;
144
        $this->productTranslationsGateway = $productTranslationsGateway;
145
        $this->categoryResolver = $categoryResolver;
146
        $this->connectGateway = $connectGateway;
147
        $this->eventManager = $eventManager;
148
        $this->categoryDenormalization = $categoryDenormalization;
149
    }
150
151
    /**
152
     * Start transaction
153
     *
154
     * Starts a transaction, which includes all insertOrUpdate and delete
155
     * operations, as well as the revision updates.
156
     *
157
     * @return void
158
     */
159
    public function startTransaction()
160
    {
161
        $this->manager->getConnection()->beginTransaction();
162
    }
163
164
    /**
165
     * Commit transaction
166
     *
167
     * Commits the transactions, once all operations are queued.
168
     *
169
     * @return void
170
     */
171
    public function commit()
172
    {
173
        $this->manager->getConnection()->commit();
174
    }
175
176
    /**
177
     * Import or update given product
178
     *
179
     * Store product in your shop database as an external product. The
180
     * associated sourceId
181
     *
182
     * @param Product $product
183
     */
184
    public function insertOrUpdate(Product $product)
185
    {
186
        /** @var Product $product */
187
        $product = $this->eventManager->filter(
188
            'Connect_ProductToShop_InsertOrUpdate_Before',
189
            $product
190
        );
191
192
        // todo@dn: Set dummy values and make product inactive
193
        if (empty($product->title) || empty($product->vendor)) {
194
            return;
195
        }
196
197
        $number = $this->generateSKU($product);
198
199
        $detail = $this->helper->getArticleDetailModelByProduct($product);
200
        $detail = $this->eventManager->filter(
201
            'Connect_Merchant_Get_Article_Detail_After',
202
            $detail,
203
            [
204
                'product' => $product,
205
                'subject' => $this
206
            ]
207
        );
208
209
        $isMainVariant = false;
210
        if ($detail === null) {
211
            $active = $this->config->getConfig('activateProductsAutomatically', false) ? true : false;
212
213
            $model = $this->getSWProductModel($product, $active, $isMainVariant);
214
215
            $detail = $this->generateNewDetail($product, $model);
216
        } else {
217
            /** @var Article $model */
218
            $model = $detail->getArticle();
219
            // fix for isMainVariant flag
220
            // in connect attribute table
221
            $mainDetail = $model->getMainDetail();
222
            $isMainVariant = $this->checkIfMainVariant($detail, $mainDetail);
223
224
            $this->updateConfiguratorSetTypeFromProduct($model, $product);
225
226
            $this->cleanUpConfiguratorSet($model, $product);
227
        }
228
229
        $detail->setNumber($number);
230
231
        $this->removeConnectImportedCategories($model);
232
233
        $detailAttribute = $this->getOrCreateAttributeModel($detail, $model);
234
235
        $connectAttribute = $this->helper->getConnectAttributeByModel($detail) ?: new ConnectAttribute;
236
        // configure main variant and groupId
237
        if ($isMainVariant === true) {
238
            $connectAttribute->setIsMainVariant(true);
239
        }
240
        $connectAttribute->setGroupId($product->groupId);
241
242
        list($updateFields, $flag) = $this->getUpdateFields($model, $detail, $connectAttribute, $product);
243
        $this->setPropertiesForNewProducts($updateFields, $model, $detailAttribute, $product);
244
245
        $this->saveVat($product, $model);
246
247
        $this->applyProductProperties($model, $product);
248
249
        $detailAttribute = $this->applyMarketplaceAttributes($detailAttribute, $product);
250
251
        $this->setConnectAttributesFromProduct($connectAttribute, $product);
252
253
        // store product categories to connect attribute
254
        $connectAttribute->setCategory($product->categories);
255
256
        $connectAttribute->setLastUpdateFlag($flag);
257
258
        $connectAttribute->setPurchasePriceHash($product->purchasePriceHash);
259
        $connectAttribute->setOfferValidUntil($product->offerValidUntil);
260
261
        $this->updateDetailFromProduct($detail, $product);
262
263
        // some shops have feature "sell not in stock",
264
        // then end customer should be able to by the product with stock = 0
265
        $shopConfiguration = $this->connectGateway->getShopConfiguration($product->shopId);
266
        if ($shopConfiguration && $shopConfiguration->sellNotInStock) {
267
            $model->setLastStock(false);
268
        } else {
269
            $model->setLastStock(true);
270
        }
271
272
        $this->detailSetUnit($detail, $product, $detailAttribute);
273
274
        $this->detailSetAttributes($detail, $product);
275
276
        $this->connectAttributeSetLastUpdate($connectAttribute, $product);
277
278
        if ($model->getMainDetail() === null) {
279
            $model->setMainDetail($detail);
280
        }
281
282
        if ($detail->getAttribute() === null) {
283
            $detail->setAttribute($detailAttribute);
284
            $detailAttribute->setArticle($model);
285
        }
286
287
        $connectAttribute->setArticle($model);
288
        $connectAttribute->setArticleDetail($detail);
289
290
        $this->eventManager->notify(
291
            'Connect_Merchant_Saving_ArticleAttribute_Before',
292
            [
293
                'subject' => $this,
294
                'connectAttribute' => $connectAttribute
295
            ]
296
        );
297
298
        $this->manager->persist($model);
299
        $this->manager->flush();
300
301
        $this->categoryResolver->storeRemoteCategories($product->categories, $model->getId());
302
        $categories = $this->categoryResolver->resolve($product->categories);
303
        if (count($categories) > 0) {
304
            $detailAttribute->setConnectMappedCategory(true);
305
        }
306
307
        $this->manager->persist($connectAttribute);
308
        $this->manager->persist($detail);
309
        //article has to be flushed
310
        $this->manager->persist($detailAttribute);
311
        $this->manager->flush();
312
313
        $this->categoryDenormalization($model, $categories);
314
315
        $defaultCustomerGroup = $this->helper->getDefaultCustomerGroup();
316
        // Only set prices, if fixedPrice is active or price updates are configured
317
        if (count($detail->getPrices()) == 0 || $connectAttribute->getFixedPrice() || $updateFields['price']) {
318
            $this->setPrice($model, $detail, $product);
319
        }
320
        // If the price is not being update, update the purchasePrice anyway
321
        $this->setPurchasePrice($detail, $product->purchasePrice, $defaultCustomerGroup);
322
323
        $this->manager->clear();
324
325
        $this->addArticleTranslations($model, $product);
326
327
        //clear cache for that article
328
        $this->helper->clearArticleCache($model->getId());
329
330
        if ($updateFields['image']) {
331
            // Reload the model in order to not to work an the already flushed model
332
            $model = $this->helper->getArticleModelByProduct($product);
333
            // import only global images for article
334
            $this->imageImport->importImagesForArticle(array_diff($product->images, $product->variantImages), $model);
0 ignored issues
show
Bug introduced by
It seems like $model defined by $this->helper->getArticleModelByProduct($product) on line 332 can be null; however, ShopwarePlugins\Connect\...mportImagesForArticle() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
339
        }
340
341
        $this->eventManager->notify(
342
            'Connect_ProductToShop_InsertOrUpdate_After',
343
            [
344
                'connectProduct' => $product,
345
                'shopArticleDetail' => $detail
346
            ]
347
        );
348
349
        $stream = $this->getOrCreateStream($product);
350
        $this->addProductToStream($stream, $model);
0 ignored issues
show
Bug introduced by
It seems like $model defined by $this->helper->getArticleModelByProduct($product) on line 332 can also be of type null; however, ShopwarePlugins\Connect\...p::addProductToStream() does only seem to accept object<Shopware\Models\Article\Article>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

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

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

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

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

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

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

$a = canBeFalseAndNull();

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

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
583
                $flag |= $key;
584
            }
585
        }
586
587
        return [$output, $flag];
588
    }
589
590
    /**
591
     * Helper method to determine if a given $fields may/must be updated.
592
     * This method will check for the model->id in order to determine, if it is a new entity. Therefore
593
     * this method cannot be used after the model in question was already flushed.
594
     *
595
     * @param $field
596
     * @param $model ProductModel
597
     * @param $attribute ConnectAttribute
598
     * @throws \RuntimeException
599
     * @return bool|null
600
     */
601
    public function isFieldUpdateAllowed($field, ProductModel $model, ConnectAttribute $attribute)
602
    {
603
        $allowed = [
604
            'ShortDescription',
605
            'LongDescription',
606
            'AdditionalDescription',
607
            'Image',
608
            'Price',
609
            'Name',
610
        ];
611
612
        // Always allow updates for new models
613
        if (!$model->getId()) {
614
            return true;
615
        }
616
617
        $field = ucfirst($field);
618
        $attributeGetter = 'getUpdate' . $field;
619
        $configName = 'overwriteProduct' . $field;
620
621
        if (!in_array($field, $allowed)) {
622
            throw new \RuntimeException("Unknown update field {$field}");
623
        }
624
625
        $attributeValue = $attribute->$attributeGetter();
626
627
628
        // If the value is 'null' or 'inherit', the behaviour will be inherited from the global configuration
629
        // Once we have a supplier based configuration, we need to take it into account here
630
        if ($attributeValue == null || $attributeValue == 'inherit') {
631
            return $this->config->getConfig($configName, true);
632
        }
633
634
        return $attributeValue == 'overwrite';
635
    }
636
637
    /**
638
     * Determine if a given field has changed
639
     *
640
     * @param $field
641
     * @param ProductModel $model
642
     * @param DetailModel $detail
643
     * @param Product $product
644
     * @return bool
645
     */
646
    public function hasFieldChanged($field, ProductModel $model, DetailModel $detail, Product $product)
647
    {
648
        switch ($field) {
649
            case 'shortDescription':
650
                return $model->getDescription() != $product->shortDescription;
651
            case 'longDescription':
652
                return $model->getDescriptionLong() != $product->longDescription;
653
            case 'additionalDescription':
654
                return $detail->getAttribute()->getConnectProductDescription() != $product->additionalDescription;
655
            case 'name':
656
                return $model->getName() != $product->title;
657
            case 'image':
658
                return count($model->getImages()) != count($product->images);
659
            case 'price':
660
                $prices = $detail->getPrices();
661
                if (empty($prices)) {
662
                    return true;
663
                }
664
                $price = $prices->first();
665
                if (!$price) {
666
                    return true;
667
                }
668
669
                return $prices->first()->getPrice() != $product->price;
670
        }
671
672
        throw new \InvalidArgumentException('Unrecognized field');
673
    }
674
675
    /**
676
     * @param array $updateFields
677
     * @param ProductModel $model
678
     * @param AttributeModel $detailAttribute
679
     * @param Product $product
680
     */
681
    private function setPropertiesForNewProducts(array $updateFields, ProductModel $model, AttributeModel $detailAttribute, Product $product)
682
    {
683
        /*
684
         * Make sure, that the following properties are set for
685
         * - new products
686
         * - products that have been configured to receive these updates
687
         */
688
        if ($updateFields['name']) {
689
            $model->setName($product->title);
690
        }
691
        if ($updateFields['shortDescription']) {
692
            $model->setDescription($product->shortDescription);
693
        }
694
        if ($updateFields['longDescription']) {
695
            $model->setDescriptionLong($product->longDescription);
696
        }
697
698
        if ($updateFields['additionalDescription']) {
699
            $detailAttribute->setConnectProductDescription($product->additionalDescription);
700
        }
701
702
        if ($product->vat !== null) {
703
            $repo = $this->manager->getRepository('Shopware\Models\Tax\Tax');
704
            $tax = round($product->vat * 100, 2);
705
            /** @var \Shopware\Models\Tax\Tax $tax */
706
            $tax = $repo->findOneBy(['tax' => $tax]);
707
            $model->setTax($tax);
708
        }
709
710
        if ($product->vendor !== null) {
711
            $repo = $this->manager->getRepository('Shopware\Models\Article\Supplier');
712
            $supplier = $repo->findOneBy(['name' => $product->vendor]);
713
            if ($supplier === null) {
714
                $supplier = $this->createSupplier($product->vendor);
715
            }
716
            $model->setSupplier($supplier);
717
        }
718
    }
719
720
    /**
721
     * @param $vendor
722
     * @return Supplier
723
     */
724
    private function createSupplier($vendor)
725
    {
726
        $supplier = new Supplier();
727
728
        if (is_array($vendor)) {
729
            $supplier->setName($vendor['name']);
730
            $supplier->setDescription($vendor['description']);
731
            if (array_key_exists('url', $vendor) && $vendor['url']) {
732
                $supplier->setLink($vendor['url']);
733
            }
734
735
            $supplier->setMetaTitle($vendor['page_title']);
736
737
            if (array_key_exists('logo_url', $vendor) && $vendor['logo_url']) {
738
                $this->imageImport->importImageForSupplier($vendor['logo_url'], $supplier);
739
            }
740
        } else {
741
            $supplier->setName($vendor);
742
        }
743
744
        //sets supplier attributes
745
        $attr = new \Shopware\Models\Attribute\ArticleSupplier();
746
        $attr->setConnectIsRemote(true);
747
748
        $supplier->setAttribute($attr);
749
750
        return $supplier;
751
    }
752
753
    /**
754
     * @param ProductModel $article
755
     * @param Product $product
756
     */
757
    private function applyProductProperties(ProductModel $article, Product $product)
758
    {
759
        if (empty($product->properties)) {
760
            return;
761
        }
762
763
        /** @var Property $firstProperty */
764
        $firstProperty = reset($product->properties);
765
        $groupRepo = $this->manager->getRepository(PropertyGroup::class);
766
        $group = $groupRepo->findOneBy(['name' => $firstProperty->groupName]);
767
768
        if (!$group) {
769
            $group = new PropertyGroup();
770
            $group->setName($firstProperty->groupName);
771
            $group->setComparable($firstProperty->comparable);
772
            $group->setSortMode($firstProperty->sortMode);
773
            $group->setPosition($firstProperty->groupPosition);
774
775
            $attribute = new \Shopware\Models\Attribute\PropertyGroup();
776
            $attribute->setPropertyGroup($group);
777
            $attribute->setConnectIsRemote(true);
778
            $group->setAttribute($attribute);
779
780
            $this->manager->persist($attribute);
781
            $this->manager->persist($group);
782
            $this->manager->flush();
783
        }
784
785
        $propertyValues = $article->getPropertyValues();
786
        $propertyValues->clear();
787
        $this->manager->persist($article);
788
        $this->manager->flush();
789
790
        $article->setPropertyGroup($group);
791
792
        $optionRepo = $this->manager->getRepository(PropertyOption::class);
793
        $valueRepo = $this->manager->getRepository(PropertyValue::class);
794
795
        foreach ($product->properties as $property) {
796
            $option = $optionRepo->findOneBy(['name' => $property->option]);
797
            $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...
798
            if (!$option) {
799
                $option = new PropertyOption();
800
                $option->setName($property->option);
801
                $option->setFilterable($property->filterable);
802
803
                $attribute = new \Shopware\Models\Attribute\PropertyOption();
804
                $attribute->setPropertyOption($option);
805
                $attribute->setConnectIsRemote(true);
806
                $option->setAttribute($attribute);
807
808
                $this->manager->persist($option);
809
                $this->manager->flush($option);
810
            }
811
812
            if (!$optionExists || !$value = $valueRepo->findOneBy(['option' => $option, 'value' => $property->value])) {
813
                $value = new PropertyValue($option, $property->value);
814
                $value->setPosition($property->valuePosition);
815
816
                $attribute = new \Shopware\Models\Attribute\PropertyValue();
817
                $attribute->setPropertyValue($value);
818
                $attribute->setConnectIsRemote(true);
819
                $value->setAttribute($attribute);
820
821
                $this->manager->persist($value);
822
            }
823
824
            if (!$propertyValues->contains($value)) {
825
                //add only new values
826
                $propertyValues->add($value);
827
            }
828
829
            $filters = [
830
                ['property' => 'options.name', 'expression' => '=', 'value' => $property->option],
831
                ['property' => 'groups.name', 'expression' => '=', 'value' => $property->groupName],
832
            ];
833
834
            $query = $groupRepo->getPropertyRelationQuery($filters, null, 1, 0);
835
            $relation = $query->getOneOrNullResult();
836
837
            if (!$relation) {
838
                $group->addOption($option);
839
                $this->manager->persist($group);
840
                $this->manager->flush($group);
841
            }
842
        }
843
844
        $article->setPropertyValues($propertyValues);
845
846
        $this->manager->persist($article);
847
        $this->manager->flush();
848
    }
849
850
    /**
851
     * Read product attributes mapping and set to shopware attribute model
852
     *
853
     * @param AttributeModel $detailAttribute
854
     * @param Product $product
855
     * @return AttributeModel
856
     */
857
    private function applyMarketplaceAttributes(AttributeModel $detailAttribute, Product $product)
858
    {
859
        $detailAttribute->setConnectReference($product->sourceId);
860
        $detailAttribute->setConnectArticleShipping($product->shipping);
861
        //todo@sb: check if connectAttribute matches position of the marketplace attribute
862
        array_walk($product->attributes, function ($value, $key) use ($detailAttribute) {
863
            $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...
864
            if (strlen($shopwareAttribute) > 0) {
865
                $setter = 'set' . ucfirst($shopwareAttribute);
866
                $detailAttribute->$setter($value);
867
            }
868
        });
869
870
        return $detailAttribute;
871
    }
872
873
    /**
874
     * @param  ConnectAttribute $connectAttribute
875
     * @param Product $product
876
     */
877
    private function setConnectAttributesFromProduct(ConnectAttribute $connectAttribute, Product $product)
878
    {
879
        $connectAttribute->setShopId($product->shopId);
880
        $connectAttribute->setSourceId($product->sourceId);
881
        $connectAttribute->setExportStatus(null);
882
        $connectAttribute->setPurchasePrice($product->purchasePrice);
883
        $connectAttribute->setFixedPrice($product->fixedPrice);
884
        $connectAttribute->setStream($product->stream);
885
886
        // store purchasePriceHash and offerValidUntil
887
        $connectAttribute->setPurchasePriceHash($product->purchasePriceHash);
888
        $connectAttribute->setOfferValidUntil($product->offerValidUntil);
889
    }
890
891
    /**
892
     * @param DetailModel $detail
893
     * @param Product $product
894
     */
895
    private function updateDetailFromProduct(DetailModel $detail, Product $product)
896
    {
897
        $detail->setInStock($product->availability);
898
        $detail->setEan($product->ean);
899
        $detail->setShippingTime($product->deliveryWorkDays);
900
        $releaseDate = new \DateTime();
901
        $releaseDate->setTimestamp($product->deliveryDate);
902
        $detail->setReleaseDate($releaseDate);
903
        $detail->setMinPurchase($product->minPurchaseQuantity);
904
    }
905
906
    /**
907
     * @param DetailModel $detail
908
     * @param Product $product
909
     * @param $detailAttribute
910
     */
911
    private function detailSetUnit(DetailModel $detail, Product $product, $detailAttribute)
912
    {
913
        // if connect product has unit
914
        // find local unit with units mapping
915
        // and add to detail model
916
        if (array_key_exists('unit', $product->attributes) && $product->attributes['unit']) {
917
            $detailAttribute->setConnectRemoteUnit($product->attributes['unit']);
918
            if ($this->config->getConfig($product->attributes['unit']) == null) {
919
                $this->config->setConfig($product->attributes['unit'], '', null, 'units');
920
            }
921
922
            /** @var \ShopwarePlugins\Connect\Components\Utils\UnitMapper $unitMapper */
923
            $unitMapper = new UnitMapper($this->config, $this->manager);
924
925
            $shopwareUnit = $unitMapper->getShopwareUnit($product->attributes['unit']);
926
927
            /** @var \Shopware\Models\Article\Unit $unit */
928
            $unit = $this->helper->getUnit($shopwareUnit);
929
            $detail->setUnit($unit);
930
            $detail->setPurchaseUnit($product->attributes['quantity']);
931
            $detail->setReferenceUnit($product->attributes['ref_quantity']);
932
        } else {
933
            $detail->setUnit(null);
934
            $detail->setPurchaseUnit(null);
935
            $detail->setReferenceUnit(null);
936
        }
937
    }
938
939
    /**
940
     * @param DetailModel $detail
941
     * @param Product $product
942
     */
943
    private function detailSetAttributes(DetailModel $detail, Product $product)
944
    {
945
        // set dimension
946
        if (array_key_exists('dimension', $product->attributes) && $product->attributes['dimension']) {
947
            $dimension = explode('x', $product->attributes['dimension']);
948
            $detail->setLen($dimension[0]);
949
            $detail->setWidth($dimension[1]);
950
            $detail->setHeight($dimension[2]);
951
        } else {
952
            $detail->setLen(null);
953
            $detail->setWidth(null);
954
            $detail->setHeight(null);
955
        }
956
957
        // set weight
958
        if (array_key_exists('weight', $product->attributes) && $product->attributes['weight']) {
959
            $detail->setWeight($product->attributes['weight']);
960
        }
961
962
        //set package unit
963
        if (array_key_exists(Product::ATTRIBUTE_PACKAGEUNIT, $product->attributes)) {
964
            $detail->setPackUnit($product->attributes[Product::ATTRIBUTE_PACKAGEUNIT]);
965
        }
966
967
        //set basic unit
968
        if (array_key_exists(Product::ATTRIBUTE_BASICUNIT, $product->attributes)) {
969
            $detail->setMinPurchase($product->attributes[Product::ATTRIBUTE_BASICUNIT]);
970
        }
971
972
        //set manufacturer no.
973
        if (array_key_exists(Product::ATTRIBUTE_MANUFACTURERNUMBER, $product->attributes)) {
974
            $detail->setSupplierNumber($product->attributes[Product::ATTRIBUTE_MANUFACTURERNUMBER]);
975
        }
976
    }
977
978
    /**
979
     * @param ConnectAttribute $connectAttribute
980
     * @param Product $product
981
     */
982
    private function connectAttributeSetLastUpdate(ConnectAttribute $connectAttribute, Product $product)
983
    {
984
        // Whenever a product is updated, store a json encoded list of all fields that are updated optionally
985
        // This way a customer will be able to apply the most recent changes any time later
986
        $connectAttribute->setLastUpdate(json_encode([
987
            'shortDescription' => $product->shortDescription,
988
            'longDescription' => $product->longDescription,
989
            'additionalDescription' => $product->additionalDescription,
990
            'purchasePrice' => $product->purchasePrice,
991
            'image' => $product->images,
992
            'variantImages' => $product->variantImages,
993
            'price' => $product->price * ($product->vat + 1),
994
            'name' => $product->title,
995
            'vat' => $product->vat
996
        ]));
997
    }
998
999
    /**
1000
     * @param ProductModel $model
1001
     * @param array $categories
1002
     */
1003
    private function categoryDenormalization(ProductModel $model, array $categories)
1004
    {
1005
        $this->categoryDenormalization->disableTransactions();
1006
        foreach ($categories as $category) {
1007
            $this->categoryDenormalization->addAssignment($model->getId(), $category);
1008
            $this->manager->getConnection()->executeQuery(
1009
                'INSERT IGNORE INTO `s_articles_categories` (`articleID`, `categoryID`) VALUES (?,?)',
1010
                [$model->getId(), $category]
1011
            );
1012
        }
1013
        $this->categoryDenormalization->enableTransactions();
1014
    }
1015
1016
    /**
1017
     * @param Product $product
1018
     * @return ProductStream
1019
     */
1020
    private function getOrCreateStream(Product $product)
1021
    {
1022
        /** @var ProductStreamRepository $repo */
1023
        $repo = $this->manager->getRepository(ProductStreamAttribute::class);
1024
        $stream = $repo->findConnectByName($product->stream);
1025
1026
        if (!$stream) {
1027
            $stream = new ProductStream();
1028
            $stream->setName($product->stream);
1029
            $stream->setType(ProductStreamService::STATIC_STREAM);
1030
            $stream->setSorting(json_encode(
1031
                [ReleaseDateSorting::class => ['direction' => 'desc']]
1032
            ));
1033
1034
            //add attributes
1035
            $attribute = new \Shopware\Models\Attribute\ProductStream();
1036
            $attribute->setProductStream($stream);
1037
            $attribute->setConnectIsRemote(true);
1038
            $stream->setAttribute($attribute);
1039
1040
            $this->manager->persist($attribute);
1041
            $this->manager->persist($stream);
1042
            $this->manager->flush();
1043
        }
1044
1045
        return $stream;
1046
    }
1047
1048
    /**
1049
     * @param ProductStream $stream
1050
     * @param ProductModel $article
1051
     * @throws \Doctrine\DBAL\DBALException
1052
     */
1053
    private function addProductToStream(ProductStream $stream, ProductModel $article)
1054
    {
1055
        $conn = $this->manager->getConnection();
1056
        $sql = 'INSERT INTO `s_product_streams_selection` (`stream_id`, `article_id`)
1057
                VALUES (:streamId, :articleId)
1058
                ON DUPLICATE KEY UPDATE stream_id = :streamId, article_id = :articleId';
1059
        $stmt = $conn->prepare($sql);
1060
        $stmt->execute([':streamId' => $stream->getId(), ':articleId' => $article->getId()]);
1061
    }
1062
1063
    /**
1064
     * Set detail purchase price with plain SQL
1065
     * Entity usage throws exception when error handlers are disabled
1066
     *
1067
     * @param ProductModel $article
1068
     * @param DetailModel $detail
1069
     * @param Product $product
1070
     * @throws \Doctrine\DBAL\DBALException
1071
     */
1072
    private function setPrice(ProductModel $article, DetailModel $detail, Product $product)
1073
    {
1074
        // set price via plain SQL because shopware throws exception
1075
        // undefined index: key when error handler is disabled
1076
        $customerGroup = $this->helper->getDefaultCustomerGroup();
1077
1078
        if (!empty($product->priceRanges)) {
1079
            $this->setPriceRange($article, $detail, $product->priceRanges, $customerGroup);
1080
1081
            return;
1082
        }
1083
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
            [$customerGroup->getKey(), 1, 'beliebig', $article->getId(), $detail->getId()]
1088
        );
1089
1090
        // todo@sb: test update prices
1091
        if ($id > 0) {
1092
            $this->manager->getConnection()->executeQuery(
1093
                'UPDATE `s_articles_prices` SET `price` = ?, `baseprice` = ? WHERE `id` = ?',
1094
                [$product->price, $product->purchasePrice, $id]
1095
            );
1096
        } else {
1097
            $this->manager->getConnection()->executeQuery(
1098
                'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`, `baseprice`)
1099
              VALUES (?, 1, "beliebig", ?, ?, ?, ?);',
1100
                [
1101
                    $customerGroup->getKey(),
1102
                    $article->getId(),
1103
                    $detail->getId(),
1104
                    $product->price,
1105
                    $product->purchasePrice
1106
                ]
1107
            );
1108
        }
1109
    }
1110
1111
    /**
1112
     * @param ProductModel $article
1113
     * @param DetailModel $detail
1114
     * @param array $priceRanges
1115
     * @param Group $group
1116
     * @throws \Doctrine\DBAL\ConnectionException
1117
     * @throws \Exception
1118
     */
1119
    private function setPriceRange(ProductModel $article, DetailModel $detail, array $priceRanges, Group $group)
1120
    {
1121
        $this->manager->getConnection()->beginTransaction();
1122
1123
        try {
1124
            // We always delete the prices,
1125
            // because we can not know which record is update
1126
            $this->manager->getConnection()->executeQuery(
1127
                'DELETE FROM `s_articles_prices` WHERE `articleID` = ? AND `articledetailsID` = ?',
1128
                [$article->getId(), $detail->getId()]
1129
            );
1130
1131
            /** @var PriceRange $priceRange */
1132
            foreach ($priceRanges as $priceRange) {
1133
                $priceTo = $priceRange->to == PriceRange::ANY ? 'beliebig' : $priceRange->to;
1134
1135
                //todo: maybe batch insert if possible?
1136
                $this->manager->getConnection()->executeQuery(
1137
                    'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`)
1138
                      VALUES (?, ?, ?, ?, ?, ?);',
1139
                    [
1140
                        $group->getKey(),
1141
                        $priceRange->from,
1142
                        $priceTo,
1143
                        $article->getId(),
1144
                        $detail->getId(),
1145
                        $priceRange->price
1146
                    ]
1147
                );
1148
            }
1149
            $this->manager->getConnection()->commit();
1150
        } catch (\Exception $e) {
1151
            $this->manager->getConnection()->rollBack();
1152
            throw new \Exception($e->getMessage());
1153
        }
1154
    }
1155
1156
    /**
1157
     * Set detail purchase price with plain SQL
1158
     * Entity usage throws exception when error handlers are disabled
1159
     *
1160
     * @param DetailModel $detail
1161
     * @param float $purchasePrice
1162
     * @param Group $defaultGroup
1163
     * @throws \Doctrine\DBAL\DBALException
1164
     */
1165
    private function setPurchasePrice(DetailModel $detail, $purchasePrice, Group $defaultGroup)
1166
    {
1167
        if (method_exists($detail, 'setPurchasePrice')) {
1168
            $this->manager->getConnection()->executeQuery(
1169
                'UPDATE `s_articles_details` SET `purchaseprice` = ? WHERE `id` = ?',
1170
                [$purchasePrice, $detail->getId()]
1171
            );
1172
        } else {
1173
            $id = $this->manager->getConnection()->fetchColumn(
1174
                'SELECT id FROM `s_articles_prices`
1175
              WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?',
1176
                [$defaultGroup->getKey(), 1, 'beliebig', $detail->getArticleId(), $detail->getId()]
1177
            );
1178
1179
            if ($id > 0) {
1180
                $this->manager->getConnection()->executeQuery(
1181
                    'UPDATE `s_articles_prices` SET `baseprice` = ? WHERE `id` = ?',
1182
                    [$purchasePrice, $id]
1183
                );
1184
            } else {
1185
                $this->manager->getConnection()->executeQuery(
1186
                    'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `baseprice`)
1187
              VALUES (?, 1, "beliebig", ?, ?, ?);',
1188
                    [$defaultGroup->getKey(), $detail->getArticleId(), $detail->getId(), $purchasePrice]
1189
                );
1190
            }
1191
        }
1192
    }
1193
1194
    /**
1195
     * Adds translation record for given article
1196
     *
1197
     * @param ProductModel $article
1198
     * @param Product $sdkProduct
1199
     */
1200
    private function addArticleTranslations(ProductModel $article, Product $sdkProduct)
1201
    {
1202
        /** @var \Shopware\Connect\Struct\Translation $translation */
1203
        foreach ($sdkProduct->translations as $key => $translation) {
1204
            /** @var \Shopware\Models\Shop\Locale $locale */
1205
            $locale = $this->getLocaleRepository()->findOneBy(['locale' => LocaleMapper::getShopwareLocale($key)]);
1206
            /** @var \Shopware\Models\Shop\Shop $shop */
1207
            $shop = $this->getShopRepository()->findOneBy(['locale' => $locale]);
1208
            if (!$shop) {
1209
                continue;
1210
            }
1211
1212
            $this->productTranslationsGateway->addArticleTranslation($translation, $article->getId(), $shop->getId());
1213
        }
1214
    }
1215
1216
    /**
1217
     * dsadsa
1218
     * @return \Shopware\Components\Model\ModelRepository
1219
     */
1220
    private function getLocaleRepository()
1221
    {
1222
        if (!$this->localeRepository) {
1223
            $this->localeRepository = $this->manager->getRepository('Shopware\Models\Shop\Locale');
1224
        }
1225
1226
        return $this->localeRepository;
1227
    }
1228
1229
    private function getShopRepository()
1230
    {
1231
        if (!$this->shopRepository) {
1232
            $this->shopRepository = $this->manager->getRepository('Shopware\Models\Shop\Shop');
1233
        }
1234
1235
        return $this->shopRepository;
1236
    }
1237
1238
    /**
1239
     * Delete product or product variant with given shopId and sourceId.
1240
     *
1241
     * Only the combination of both identifies a product uniquely. Do NOT
1242
     * delete products just by their sourceId.
1243
     *
1244
     * You might receive delete requests for products, which are not available
1245
     * in your shop. Just ignore them.
1246
     *
1247
     * @param string $shopId
1248
     * @param string $sourceId
1249
     * @return void
1250
     */
1251
    public function delete($shopId, $sourceId)
1252
    {
1253
        $detail = $this->helper->getArticleDetailModelByProduct(new Product([
1254
            'shopId' => $shopId,
1255
            'sourceId' => $sourceId,
1256
        ]));
1257
        if ($detail === null) {
1258
            return;
1259
        }
1260
1261
        $this->deleteDetail($detail);
1262
    }
1263
1264
    public function update($shopId, $sourceId, ProductUpdate $product)
1265
    {
1266
        // find article detail id
1267
        $articleDetailId = $this->manager->getConnection()->fetchColumn(
1268
            'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?',
1269
            [$sourceId, $shopId]
1270
        );
1271
1272
        $this->eventManager->notify(
1273
            'Connect_Merchant_Update_GeneralProductInformation',
1274
            [
1275
                'subject' => $this,
1276
                'shopId' => $shopId,
1277
                'sourceId' => $sourceId,
1278
                'articleDetailId' => $articleDetailId
1279
            ]
1280
        );
1281
1282
        // update purchasePriceHash, offerValidUntil and purchasePrice in connect attribute
1283
        $this->manager->getConnection()->executeUpdate(
1284
            'UPDATE s_plugin_connect_items SET purchase_price_hash = ?, offer_valid_until = ?, purchase_price = ?
1285
            WHERE source_id = ? AND shop_id = ?',
1286
            [
1287
                $product->purchasePriceHash,
1288
                $product->offerValidUntil,
1289
                $product->purchasePrice,
1290
                $sourceId,
1291
                $shopId,
1292
            ]
1293
        );
1294
1295
        // update stock in article detail
1296
        // update prices
1297
        // if purchase price is stored in article detail
1298
        // update it together with stock
1299
        // since shopware 5.2
1300
        if (method_exists('Shopware\Models\Article\Detail', 'getPurchasePrice')) {
1301
            $this->manager->getConnection()->executeUpdate(
1302
                'UPDATE s_articles_details SET instock = ?, purchaseprice = ? WHERE id = ?',
1303
                [$product->availability, $product->purchasePrice, $articleDetailId]
1304
            );
1305
        } else {
1306
            $this->manager->getConnection()->executeUpdate(
1307
                'UPDATE s_articles_details SET instock = ? WHERE id = ?',
1308
                [$product->availability, $articleDetailId]
1309
            );
1310
        }
1311
        $this->manager->getConnection()->executeUpdate(
1312
            "UPDATE s_articles_prices SET price = ?, baseprice = ? WHERE articledetailsID = ? AND pricegroup = 'EK'",
1313
            [$product->price, $product->purchasePrice, $articleDetailId]
1314
        );
1315
    }
1316
1317
    public function changeAvailability($shopId, $sourceId, $availability)
1318
    {
1319
        // find article detail id
1320
        $articleDetailId = $this->manager->getConnection()->fetchColumn(
1321
            'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?',
1322
            [$sourceId, $shopId]
1323
        );
1324
1325
        $this->eventManager->notify(
1326
            'Connect_Merchant_Update_GeneralProductInformation',
1327
            [
1328
                'subject' => $this,
1329
                'shopId' => $shopId,
1330
                'sourceId' => $sourceId,
1331
                'articleDetailId' => $articleDetailId
1332
            ]
1333
        );
1334
1335
        // update stock in article detail
1336
        $this->manager->getConnection()->executeUpdate(
1337
            'UPDATE s_articles_details SET instock = ? WHERE id = ?',
1338
            [$availability, $articleDetailId]
1339
        );
1340
    }
1341
1342
    /**
1343
     * @inheritDoc
1344
     */
1345
    public function makeMainVariant($shopId, $sourceId, $groupId)
1346
    {
1347
        //find article detail which should be selected as main one
1348
        $newMainDetail = $this->helper->getConnectArticleDetailModel($sourceId, $shopId);
1349
        if (!$newMainDetail) {
1350
            return;
1351
        }
1352
1353
        /** @var \Shopware\Models\Article\Article $article */
1354
        $article = $newMainDetail->getArticle();
1355
1356
        $this->eventManager->notify(
1357
            'Connect_Merchant_Update_ProductMainVariant_Before',
1358
            [
1359
                'subject' => $this,
1360
                'shopId' => $shopId,
1361
                'sourceId' => $sourceId,
1362
                'articleId' => $article->getId(),
1363
                'articleDetailId' => $newMainDetail->getId()
1364
            ]
1365
        );
1366
1367
        // replace current main detail with new one
1368
        $currentMainDetail = $article->getMainDetail();
1369
        $currentMainDetail->setKind(2);
1370
        $newMainDetail->setKind(1);
1371
        $article->setMainDetail($newMainDetail);
1372
1373
        $this->manager->persist($newMainDetail);
1374
        $this->manager->persist($currentMainDetail);
1375
        $this->manager->persist($article);
1376
        $this->manager->flush();
1377
    }
1378
1379
    /**
1380
     * Updates the status of an Order
1381
     *
1382
     * @param string $localOrderId
1383
     * @param string $orderStatus
1384
     * @param string $trackingNumber
1385
     * @return void
1386
     */
1387
    public function updateOrderStatus($localOrderId, $orderStatus, $trackingNumber)
1388
    {
1389
        if ($this->config->getConfig('updateOrderStatus') == 1) {
1390
            $this->updateDeliveryStatus($localOrderId, $orderStatus);
1391
        }
1392
1393
        if ($trackingNumber) {
1394
            $this->updateTrackingNumber($localOrderId, $trackingNumber);
1395
        }
1396
    }
1397
1398
    /**
1399
     * @param string $localOrderId
1400
     * @param string $orderStatus
1401
     */
1402
    private function updateDeliveryStatus($localOrderId, $orderStatus)
1403
    {
1404
        $status = false;
1405
        if ($orderStatus === OrderStatus::STATE_IN_PROCESS) {
1406
            $status = Status::ORDER_STATE_PARTIALLY_DELIVERED;
1407
        } elseif ($orderStatus === OrderStatus::STATE_DELIVERED) {
1408
            $status = Status::ORDER_STATE_COMPLETELY_DELIVERED;
1409
        }
1410
1411
        if ($status) {
1412
            $this->manager->getConnection()->executeQuery(
1413
                'UPDATE s_order 
1414
                SET status = :orderStatus
1415
                WHERE ordernumber = :orderNumber',
1416
                [
1417
                    ':orderStatus' => $status,
1418
                    ':orderNumber' => $localOrderId
1419
                ]
1420
            );
1421
        }
1422
    }
1423
1424
    /**
1425
     * @param string $localOrderId
1426
     * @param string $trackingNumber
1427
     */
1428
    private function updateTrackingNumber($localOrderId, $trackingNumber)
1429
    {
1430
        $currentTrackingCode = $this->manager->getConnection()->fetchColumn(
1431
            'SELECT trackingcode
1432
            FROM s_order
1433
            WHERE ordernumber = :orderNumber',
1434
            [
1435
                ':orderNumber' => $localOrderId
1436
            ]
1437
        );
1438
1439
        if (!$currentTrackingCode) {
1440
            $newTracking = $trackingNumber;
1441
        } else {
1442
            $newTracking = $this->combineTrackingNumbers($trackingNumber, $currentTrackingCode);
1443
        }
1444
1445
        $this->manager->getConnection()->executeQuery(
1446
            'UPDATE s_order 
1447
            SET trackingcode = :trackingCode
1448
            WHERE ordernumber = :orderNumber',
1449
            [
1450
                ':trackingCode' => $newTracking,
1451
                ':orderNumber' => $localOrderId
1452
            ]
1453
        );
1454
    }
1455
1456
    /**
1457
     * @param string $newTrackingCode
1458
     * @param string $currentTrackingCode
1459
     * @return string
1460
     */
1461
    private function combineTrackingNumbers($newTrackingCode, $currentTrackingCode)
1462
    {
1463
        $currentTrackingCodes = $this->getTrackingNumberAsArray($currentTrackingCode);
1464
        $newTrackingCodes = $this->getTrackingNumberAsArray($newTrackingCode);
1465
        $newTrackingCodes = array_unique(array_merge($currentTrackingCodes, $newTrackingCodes));
1466
        $newTracking = implode(',', $newTrackingCodes);
1467
1468
        return $newTracking;
1469
    }
1470
1471
    /**
1472
     * @param string $trackingCode
1473
     * @return string[]
1474
     */
1475
    private function getTrackingNumberAsArray($trackingCode)
1476
    {
1477
        if (strpos($trackingCode, ',') !== false) {
1478
            return explode(',', $trackingCode);
1479
        }
1480
1481
        return [$trackingCode];
1482
    }
1483
1484
    /**
1485
     * @param Product $product
1486
     * @param ProductModel $model
1487
     */
1488
    private function saveVat(Product $product, ProductModel $model)
1489
    {
1490
        if ($product->vat !== null) {
1491
            $repo = $this->manager->getRepository(Tax::class);
1492
            $taxRate = round($product->vat * 100, 2);
1493
            /** @var \Shopware\Models\Tax\Tax $tax */
1494
            $tax = $repo->findOneBy(['tax' => $taxRate]);
1495
            if (!$tax) {
1496
                $tax = new Tax();
1497
                $tax->setTax($taxRate);
1498
                //this is to get rid of zeroes behind the decimal point
1499
                $name = strval(round($taxRate, 2)) . '%';
1500
                $tax->setName($name);
1501
                $this->manager->persist($tax);
1502
            }
1503
            $model->setTax($tax);
1504
        }
1505
    }
1506
}
1507