Completed
Pull Request — master (#377)
by Jonas
03:33
created

ProductToShop::createSupplier()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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