Completed
Pull Request — master (#333)
by Simon
04:47
created

ProductToShop::delete()   B

Complexity

Conditions 8
Paths 33

Size

Total Lines 56
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 56
rs 7.3333
c 0
b 0
f 0
cc 8
eloc 31
nc 33
nop 2

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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