Completed
Pull Request — master (#392)
by Christian
03:39
created

ProductToShop::getSWProductModel()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

Loading history...
442
                $model = $this->helper->createProductModel($product);
443
                $model->setActive($active);
444
            }
445
        }
446
447
        return $model;
448
    }
449
450
    /**
451
     * @param Product $product
452
     * @param $model
453
     * @return DetailModel
454
     */
455
    private function generateNewDetail(Product $product, $model)
456
    {
457
        $detail = new DetailModel();
458
        $detail->setActive($model->getActive());
459
        $this->manager->persist($detail);
460
        $detail->setArticle($model);
461
        $model->getDetails()->add($detail);
462
        if (!empty($product->variant)) {
463
            $this->variantConfigurator->configureVariantAttributes($product, $detail);
464
        }
465
466
        return $detail;
467
    }
468
469
    /**
470
     * @param $detail
471
     * @param $mainDetail
472
     * @return bool
473
     */
474
    private function checkIfMainVariant($detail, $mainDetail)
475
    {
476
        return $detail->getId() === $mainDetail->getId();
477
    }
478
479
    /**
480
     * @param ProductModel $model
481
     * @param Product $product
482
     */
483
    private function updateConfiguratorSetTypeFromProduct(ProductModel $model, Product $product)
484
    {
485
        if (!empty($product->variant)) {
486
            $configSet = $model->getConfiguratorSet();
487
            $configSet->setType($product->configuratorSetType);
488
        }
489
    }
490
491
    /**
492
     * @param ProductModel $model
493
     * @param Product $product
494
     */
495
    private function cleanUpConfiguratorSet(ProductModel $model, Product $product)
496
    {
497
        if (empty($product->variant) && $model->getConfiguratorSet()) {
498
            $this->manager->getConnection()->executeQuery(
499
                'UPDATE s_articles SET configurator_set_id = NULL WHERE id = ?',
500
                [$model->getId()]
501
            );
502
        }
503
    }
504
505
    /**
506
     * @param ProductModel $model
507
     */
508
    private function removeConnectImportedCategories(ProductModel $model)
509
    {
510
        /** @var \Shopware\Models\Category\Category $category */
511
        foreach ($model->getCategories() as $category) {
512
            $attribute = $category->getAttribute();
513
            if (!$attribute) {
514
                continue;
515
            }
516
517
            if ($attribute->getConnectImported()) {
518
                $model->removeCategory($category);
519
            }
520
        }
521
    }
522
523
    /**
524
     * @param DetailModel $detail
525
     * @param ProductModel $model
526
     * @return AttributeModel
527
     */
528
    private function getOrCreateAttributeModel(DetailModel $detail, ProductModel $model)
529
    {
530
        $detailAttribute = $detail->getAttribute();
531
        if (!$detailAttribute) {
532
            $detailAttribute = new AttributeModel();
533
            $detail->setAttribute($detailAttribute);
534
            $model->setAttribute($detailAttribute);
535
            $detailAttribute->setArticle($model);
536
            $detailAttribute->setArticleDetail($detail);
537
        }
538
539
        return $detailAttribute;
540
    }
541
542
    /**
543
     * Get array of update info for the known fields
544
     *
545
     * @param $model
546
     * @param $detail
547
     * @param $attribute
548
     * @param $product
549
     * @return array
550
     */
551
    public function getUpdateFields($model, $detail, $attribute, $product)
552
    {
553
        // This also defines the flags of these fields
554
        $fields = $this->helper->getUpdateFlags();
555
        $flagsByName = array_flip($fields);
556
557
        $flag = 0;
558
        $output = [];
559
        foreach ($fields as $key => $field) {
560
            // Don't handle the imageInitialImport flag
561
            if ($field == 'imageInitialImport') {
562
                continue;
563
            }
564
565
            // If this is a new product
566
            if (!$model->getId() && $field == 'image' && !$this->config->getConfig('importImagesOnFirstImport',
567
                    false)) {
568
                $output[$field] = false;
569
                $flag |= $flagsByName['imageInitialImport'];
570
                continue;
571
            }
572
573
            $updateAllowed = $this->isFieldUpdateAllowed($field, $model, $attribute);
574
            $output[$field] = $updateAllowed;
575
            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...
576
                $flag |= $key;
577
            }
578
        }
579
580
        return [$output, $flag];
581
    }
582
583
    /**
584
     * Helper method to determine if a given $fields may/must be updated.
585
     * This method will check for the model->id in order to determine, if it is a new entity. Therefore
586
     * this method cannot be used after the model in question was already flushed.
587
     *
588
     * @param $field
589
     * @param $model ProductModel
590
     * @param $attribute ConnectAttribute
591
     * @throws \RuntimeException
592
     * @return bool|null
593
     */
594
    public function isFieldUpdateAllowed($field, ProductModel $model, ConnectAttribute $attribute)
595
    {
596
        $allowed = [
597
            'ShortDescription',
598
            'LongDescription',
599
            'AdditionalDescription',
600
            'Image',
601
            'Price',
602
            'Name',
603
        ];
604
605
        // Always allow updates for new models
606
        if (!$model->getId()) {
607
            return true;
608
        }
609
610
        $field = ucfirst($field);
611
        $attributeGetter = 'getUpdate' . $field;
612
        $configName = 'overwriteProduct' . $field;
613
614
        if (!in_array($field, $allowed)) {
615
            throw new \RuntimeException("Unknown update field {$field}");
616
        }
617
618
        $attributeValue = $attribute->$attributeGetter();
619
620
621
        // If the value is 'null' or 'inherit', the behaviour will be inherited from the global configuration
622
        // Once we have a supplier based configuration, we need to take it into account here
623
        if ($attributeValue == null || $attributeValue == 'inherit') {
624
            return $this->config->getConfig($configName, true);
625
        }
626
627
        return $attributeValue == 'overwrite';
628
    }
629
630
    /**
631
     * Determine if a given field has changed
632
     *
633
     * @param $field
634
     * @param ProductModel $model
635
     * @param DetailModel $detail
636
     * @param Product $product
637
     * @return bool
638
     */
639
    public function hasFieldChanged($field, ProductModel $model, DetailModel $detail, Product $product)
640
    {
641
        switch ($field) {
642
            case 'shortDescription':
643
                return $model->getDescription() != $product->shortDescription;
644
            case 'longDescription':
645
                return $model->getDescriptionLong() != $product->longDescription;
646
            case 'additionalDescription':
647
                return $detail->getAttribute()->getConnectProductDescription() != $product->additionalDescription;
648
            case 'name':
649
                return $model->getName() != $product->title;
650
            case 'image':
651
                return count($model->getImages()) != count($product->images);
652
            case 'price':
653
                $prices = $detail->getPrices();
654
                if (empty($prices)) {
655
                    return true;
656
                }
657
                $price = $prices->first();
658
                if (!$price) {
659
                    return true;
660
                }
661
662
                return $prices->first()->getPrice() != $product->price;
663
        }
664
665
        throw new \InvalidArgumentException('Unrecognized field');
666
    }
667
668
    /**
669
     * @param array $updateFields
670
     * @param ProductModel $model
671
     * @param AttributeModel $detailAttribute
672
     * @param Product $product
673
     */
674
    private function setPropertiesForNewProducts(array $updateFields, ProductModel $model, AttributeModel $detailAttribute, Product $product)
675
    {
676
        /*
677
         * Make sure, that the following properties are set for
678
         * - new products
679
         * - products that have been configured to receive these updates
680
         */
681
        if ($updateFields['name']) {
682
            $model->setName($product->title);
683
        }
684
        if ($updateFields['shortDescription']) {
685
            $model->setDescription($product->shortDescription);
686
        }
687
        if ($updateFields['longDescription']) {
688
            $model->setDescriptionLong($product->longDescription);
689
        }
690
691
        if ($updateFields['additionalDescription']) {
692
            $detailAttribute->setConnectProductDescription($product->additionalDescription);
693
        }
694
695
        if ($product->vat !== null) {
696
            $repo = $this->manager->getRepository('Shopware\Models\Tax\Tax');
697
            $tax = round($product->vat * 100, 2);
698
            /** @var \Shopware\Models\Tax\Tax $tax */
699
            $tax = $repo->findOneBy(['tax' => $tax]);
700
            $model->setTax($tax);
701
        }
702
703
        if ($product->vendor !== null) {
704
            $repo = $this->manager->getRepository('Shopware\Models\Article\Supplier');
705
            $supplier = $repo->findOneBy(['name' => $product->vendor]);
706
            if ($supplier === null) {
707
                $supplier = $this->createSupplier($product->vendor);
708
            }
709
            $model->setSupplier($supplier);
710
        }
711
    }
712
713
    /**
714
     * @param $vendor
715
     * @return Supplier
716
     */
717
    private function createSupplier($vendor)
718
    {
719
        $supplier = new Supplier();
720
721
        if (is_array($vendor)) {
722
            $supplier->setName($vendor['name']);
723
            $supplier->setDescription($vendor['description']);
724
            if (array_key_exists('url', $vendor) && $vendor['url']) {
725
                $supplier->setLink($vendor['url']);
726
            }
727
728
            $supplier->setMetaTitle($vendor['page_title']);
729
730
            if (array_key_exists('logo_url', $vendor) && $vendor['logo_url']) {
731
                $this->imageImport->importImageForSupplier($vendor['logo_url'], $supplier);
732
            }
733
        } else {
734
            $supplier->setName($vendor);
735
        }
736
737
        //sets supplier attributes
738
        $attr = new \Shopware\Models\Attribute\ArticleSupplier();
739
        $attr->setConnectIsRemote(true);
740
741
        $supplier->setAttribute($attr);
742
743
        return $supplier;
744
    }
745
746
    /**
747
     * @param ProductModel $article
748
     * @param Product $product
749
     */
750
    private function applyProductProperties(ProductModel $article, Product $product)
751
    {
752
        if (empty($product->properties)) {
753
            return;
754
        }
755
756
        /** @var Property $firstProperty */
757
        $firstProperty = reset($product->properties);
758
        $groupRepo = $this->manager->getRepository(PropertyGroup::class);
759
        $group = $groupRepo->findOneBy(['name' => $firstProperty->groupName]);
760
761
        if (!$group) {
762
            $group = new PropertyGroup();
763
            $group->setName($firstProperty->groupName);
764
            $group->setComparable($firstProperty->comparable);
765
            $group->setSortMode($firstProperty->sortMode);
766
            $group->setPosition($firstProperty->groupPosition);
767
768
            $attribute = new \Shopware\Models\Attribute\PropertyGroup();
769
            $attribute->setPropertyGroup($group);
770
            $attribute->setConnectIsRemote(true);
771
            $group->setAttribute($attribute);
772
773
            $this->manager->persist($attribute);
774
            $this->manager->persist($group);
775
            $this->manager->flush();
776
        }
777
778
        $propertyValues = $article->getPropertyValues();
779
        $propertyValues->clear();
780
        $this->manager->persist($article);
781
        $this->manager->flush();
782
783
        $article->setPropertyGroup($group);
784
785
        $optionRepo = $this->manager->getRepository(PropertyOption::class);
786
        $valueRepo = $this->manager->getRepository(PropertyValue::class);
787
788
        foreach ($product->properties as $property) {
789
            $option = $optionRepo->findOneBy(['name' => $property->option]);
790
            $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...
791
            if (!$option) {
792
                $option = new PropertyOption();
793
                $option->setName($property->option);
794
                $option->setFilterable($property->filterable);
795
796
                $attribute = new \Shopware\Models\Attribute\PropertyOption();
797
                $attribute->setPropertyOption($option);
798
                $attribute->setConnectIsRemote(true);
799
                $option->setAttribute($attribute);
800
801
                $this->manager->persist($option);
802
                $this->manager->flush($option);
803
            }
804
805
            if (!$optionExists || !$value = $valueRepo->findOneBy(['option' => $option, 'value' => $property->value])) {
806
                $value = new PropertyValue($option, $property->value);
807
                $value->setPosition($property->valuePosition);
808
809
                $attribute = new \Shopware\Models\Attribute\PropertyValue();
810
                $attribute->setPropertyValue($value);
811
                $attribute->setConnectIsRemote(true);
812
                $value->setAttribute($attribute);
813
814
                $this->manager->persist($value);
815
            }
816
817
            if (!$propertyValues->contains($value)) {
818
                //add only new values
819
                $propertyValues->add($value);
820
            }
821
822
            $filters = [
823
                ['property' => 'options.name', 'expression' => '=', 'value' => $property->option],
824
                ['property' => 'groups.name', 'expression' => '=', 'value' => $property->groupName],
825
            ];
826
827
            $query = $groupRepo->getPropertyRelationQuery($filters, null, 1, 0);
828
            $relation = $query->getOneOrNullResult();
829
830
            if (!$relation) {
831
                $group->addOption($option);
832
                $this->manager->persist($group);
833
                $this->manager->flush($group);
834
            }
835
        }
836
837
        $article->setPropertyValues($propertyValues);
838
839
        $this->manager->persist($article);
840
        $this->manager->flush();
841
    }
842
843
    /**
844
     * Read product attributes mapping and set to shopware attribute model
845
     *
846
     * @param AttributeModel $detailAttribute
847
     * @param Product $product
848
     * @return AttributeModel
849
     */
850
    private function applyMarketplaceAttributes(AttributeModel $detailAttribute, Product $product)
851
    {
852
        $detailAttribute->setConnectReference($product->sourceId);
853
        $detailAttribute->setConnectArticleShipping($product->shipping);
854
        //todo@sb: check if connectAttribute matches position of the marketplace attribute
855
        array_walk($product->attributes, function ($value, $key) use ($detailAttribute) {
856
            $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...
857
            if (strlen($shopwareAttribute) > 0) {
858
                $setter = 'set' . ucfirst($shopwareAttribute);
859
                $detailAttribute->$setter($value);
860
            }
861
        });
862
863
        return $detailAttribute;
864
    }
865
866
    /**
867
     * @param  ConnectAttribute $connectAttribute
868
     * @param Product $product
869
     */
870
    private function setConnectAttributesFromProduct(ConnectAttribute $connectAttribute, Product $product)
871
    {
872
        $connectAttribute->setShopId($product->shopId);
873
        $connectAttribute->setSourceId($product->sourceId);
874
        $connectAttribute->setExportStatus(null);
875
        $connectAttribute->setPurchasePrice($product->purchasePrice);
876
        $connectAttribute->setFixedPrice($product->fixedPrice);
877
        $connectAttribute->setStream($product->stream);
878
879
        // store purchasePriceHash and offerValidUntil
880
        $connectAttribute->setPurchasePriceHash($product->purchasePriceHash);
881
        $connectAttribute->setOfferValidUntil($product->offerValidUntil);
882
    }
883
884
    /**
885
     * @param DetailModel $detail
886
     * @param Product $product
887
     */
888
    private function updateDetailFromProduct(DetailModel $detail, Product $product)
889
    {
890
        $detail->setInStock($product->availability);
891
        $detail->setEan($product->ean);
892
        $detail->setShippingTime($product->deliveryWorkDays);
893
        $releaseDate = new \DateTime();
894
        $releaseDate->setTimestamp($product->deliveryDate);
895
        $detail->setReleaseDate($releaseDate);
896
        $detail->setMinPurchase($product->minPurchaseQuantity);
897
    }
898
899
    /**
900
     * @param DetailModel $detail
901
     * @param Product $product
902
     * @param $detailAttribute
903
     */
904
    private function detailSetUnit(DetailModel $detail, Product $product, $detailAttribute)
905
    {
906
        // if connect product has unit
907
        // find local unit with units mapping
908
        // and add to detail model
909
        if (array_key_exists('unit', $product->attributes) && $product->attributes['unit']) {
910
            $detailAttribute->setConnectRemoteUnit($product->attributes['unit']);
911
            if ($this->config->getConfig($product->attributes['unit']) == null) {
912
                $this->config->setConfig($product->attributes['unit'], '', null, 'units');
913
            }
914
915
            /** @var \ShopwarePlugins\Connect\Components\Utils\UnitMapper $unitMapper */
916
            $unitMapper = new UnitMapper($this->config, $this->manager);
917
918
            $shopwareUnit = $unitMapper->getShopwareUnit($product->attributes['unit']);
919
920
            /** @var \Shopware\Models\Article\Unit $unit */
921
            $unit = $this->helper->getUnit($shopwareUnit);
922
            $detail->setUnit($unit);
923
            $detail->setPurchaseUnit($product->attributes['quantity']);
924
            $detail->setReferenceUnit($product->attributes['ref_quantity']);
925
        } else {
926
            $detail->setUnit(null);
927
            $detail->setPurchaseUnit(null);
928
            $detail->setReferenceUnit(null);
929
        }
930
    }
931
932
    /**
933
     * @param DetailModel $detail
934
     * @param Product $product
935
     */
936
    private function detailSetAttributes(DetailModel $detail, Product $product)
937
    {
938
        // set dimension
939
        if (array_key_exists('dimension', $product->attributes) && $product->attributes['dimension']) {
940
            $dimension = explode('x', $product->attributes['dimension']);
941
            $detail->setLen($dimension[0]);
942
            $detail->setWidth($dimension[1]);
943
            $detail->setHeight($dimension[2]);
944
        } else {
945
            $detail->setLen(null);
946
            $detail->setWidth(null);
947
            $detail->setHeight(null);
948
        }
949
950
        // set weight
951
        if (array_key_exists('weight', $product->attributes) && $product->attributes['weight']) {
952
            $detail->setWeight($product->attributes['weight']);
953
        }
954
955
        //set package unit
956
        if (array_key_exists(Product::ATTRIBUTE_PACKAGEUNIT, $product->attributes)) {
957
            $detail->setPackUnit($product->attributes[Product::ATTRIBUTE_PACKAGEUNIT]);
958
        }
959
960
        //set basic unit
961
        if (array_key_exists(Product::ATTRIBUTE_BASICUNIT, $product->attributes)) {
962
            $detail->setMinPurchase($product->attributes[Product::ATTRIBUTE_BASICUNIT]);
963
        }
964
965
        //set manufacturer no.
966
        if (array_key_exists(Product::ATTRIBUTE_MANUFACTURERNUMBER, $product->attributes)) {
967
            $detail->setSupplierNumber($product->attributes[Product::ATTRIBUTE_MANUFACTURERNUMBER]);
968
        }
969
    }
970
971
    /**
972
     * @param ConnectAttribute $connectAttribute
973
     * @param Product $product
974
     */
975
    private function connectAttributeSetLastUpdate(ConnectAttribute $connectAttribute, Product $product)
976
    {
977
        // Whenever a product is updated, store a json encoded list of all fields that are updated optionally
978
        // This way a customer will be able to apply the most recent changes any time later
979
        $connectAttribute->setLastUpdate(json_encode([
980
            'shortDescription' => $product->shortDescription,
981
            'longDescription' => $product->longDescription,
982
            'additionalDescription' => $product->additionalDescription,
983
            'purchasePrice' => $product->purchasePrice,
984
            'image' => $product->images,
985
            'variantImages' => $product->variantImages,
986
            'price' => $product->price * ($product->vat + 1),
987
            'name' => $product->title,
988
            'vat' => $product->vat
989
        ]));
990
    }
991
992
    /**
993
     * @param ProductModel $model
994
     * @param array $categories
995
     */
996
    private function categoryDenormalization(ProductModel $model, array $categories)
997
    {
998
        $this->categoryDenormalization->disableTransactions();
999
        foreach ($categories as $category) {
1000
            $this->categoryDenormalization->addAssignment($model->getId(), $category);
1001
            $this->manager->getConnection()->executeQuery(
1002
                'INSERT IGNORE INTO `s_articles_categories` (`articleID`, `categoryID`) VALUES (?,?)',
1003
                [$model->getId(), $category]
1004
            );
1005
        }
1006
        $this->categoryDenormalization->enableTransactions();
1007
    }
1008
1009
    /**
1010
     * Set detail purchase price with plain SQL
1011
     * Entity usage throws exception when error handlers are disabled
1012
     *
1013
     * @param ProductModel $article
1014
     * @param DetailModel $detail
1015
     * @param Product $product
1016
     * @throws \Doctrine\DBAL\DBALException
1017
     */
1018
    private function setPrice(ProductModel $article, DetailModel $detail, Product $product)
1019
    {
1020
        // set price via plain SQL because shopware throws exception
1021
        // undefined index: key when error handler is disabled
1022
        $customerGroup = $this->helper->getDefaultCustomerGroup();
1023
1024
        if (!empty($product->priceRanges)) {
1025
            $this->setPriceRange($article, $detail, $product->priceRanges, $customerGroup);
1026
1027
            return;
1028
        }
1029
1030
        $id = $this->manager->getConnection()->fetchColumn(
1031
            'SELECT id FROM `s_articles_prices`
1032
              WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?',
1033
            [$customerGroup->getKey(), 1, 'beliebig', $article->getId(), $detail->getId()]
1034
        );
1035
1036
        // todo@sb: test update prices
1037
        if ($id > 0) {
1038
            $this->manager->getConnection()->executeQuery(
1039
                'UPDATE `s_articles_prices` SET `price` = ?, `baseprice` = ? WHERE `id` = ?',
1040
                [$product->price, $product->purchasePrice, $id]
1041
            );
1042
        } else {
1043
            $this->manager->getConnection()->executeQuery(
1044
                'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`, `baseprice`)
1045
              VALUES (?, 1, "beliebig", ?, ?, ?, ?);',
1046
                [
1047
                    $customerGroup->getKey(),
1048
                    $article->getId(),
1049
                    $detail->getId(),
1050
                    $product->price,
1051
                    $product->purchasePrice
1052
                ]
1053
            );
1054
        }
1055
    }
1056
1057
    /**
1058
     * @param ProductModel $article
1059
     * @param DetailModel $detail
1060
     * @param array $priceRanges
1061
     * @param Group $group
1062
     * @throws \Doctrine\DBAL\ConnectionException
1063
     * @throws \Exception
1064
     */
1065
    private function setPriceRange(ProductModel $article, DetailModel $detail, array $priceRanges, Group $group)
1066
    {
1067
        $this->manager->getConnection()->beginTransaction();
1068
1069
        try {
1070
            // We always delete the prices,
1071
            // because we can not know which record is update
1072
            $this->manager->getConnection()->executeQuery(
1073
                'DELETE FROM `s_articles_prices` WHERE `articleID` = ? AND `articledetailsID` = ?',
1074
                [$article->getId(), $detail->getId()]
1075
            );
1076
1077
            /** @var PriceRange $priceRange */
1078
            foreach ($priceRanges as $priceRange) {
1079
                $priceTo = $priceRange->to == PriceRange::ANY ? 'beliebig' : $priceRange->to;
1080
1081
                //todo: maybe batch insert if possible?
1082
                $this->manager->getConnection()->executeQuery(
1083
                    'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`)
1084
                      VALUES (?, ?, ?, ?, ?, ?);',
1085
                    [
1086
                        $group->getKey(),
1087
                        $priceRange->from,
1088
                        $priceTo,
1089
                        $article->getId(),
1090
                        $detail->getId(),
1091
                        $priceRange->price
1092
                    ]
1093
                );
1094
            }
1095
            $this->manager->getConnection()->commit();
1096
        } catch (\Exception $e) {
1097
            $this->manager->getConnection()->rollBack();
1098
            throw new \Exception($e->getMessage());
1099
        }
1100
    }
1101
1102
    /**
1103
     * Set detail purchase price with plain SQL
1104
     * Entity usage throws exception when error handlers are disabled
1105
     *
1106
     * @param DetailModel $detail
1107
     * @param float $purchasePrice
1108
     * @param Group $defaultGroup
1109
     * @throws \Doctrine\DBAL\DBALException
1110
     */
1111
    private function setPurchasePrice(DetailModel $detail, $purchasePrice, Group $defaultGroup)
1112
    {
1113
        if (method_exists($detail, 'setPurchasePrice')) {
1114
            $this->manager->getConnection()->executeQuery(
1115
                'UPDATE `s_articles_details` SET `purchaseprice` = ? WHERE `id` = ?',
1116
                [$purchasePrice, $detail->getId()]
1117
            );
1118
        } else {
1119
            $id = $this->manager->getConnection()->fetchColumn(
1120
                'SELECT id FROM `s_articles_prices`
1121
              WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?',
1122
                [$defaultGroup->getKey(), 1, 'beliebig', $detail->getArticleId(), $detail->getId()]
1123
            );
1124
1125
            if ($id > 0) {
1126
                $this->manager->getConnection()->executeQuery(
1127
                    'UPDATE `s_articles_prices` SET `baseprice` = ? WHERE `id` = ?',
1128
                    [$purchasePrice, $id]
1129
                );
1130
            } else {
1131
                $this->manager->getConnection()->executeQuery(
1132
                    'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `baseprice`)
1133
              VALUES (?, 1, "beliebig", ?, ?, ?);',
1134
                    [$defaultGroup->getKey(), $detail->getArticleId(), $detail->getId(), $purchasePrice]
1135
                );
1136
            }
1137
        }
1138
    }
1139
1140
    /**
1141
     * Adds translation record for given article
1142
     *
1143
     * @param ProductModel $article
1144
     * @param Product $sdkProduct
1145
     */
1146
    private function addArticleTranslations(ProductModel $article, Product $sdkProduct)
1147
    {
1148
        /** @var \Shopware\Connect\Struct\Translation $translation */
1149
        foreach ($sdkProduct->translations as $key => $translation) {
1150
            /** @var \Shopware\Models\Shop\Locale $locale */
1151
            $locale = $this->getLocaleRepository()->findOneBy(['locale' => LocaleMapper::getShopwareLocale($key)]);
1152
            /** @var \Shopware\Models\Shop\Shop $shop */
1153
            $shop = $this->getShopRepository()->findOneBy(['locale' => $locale]);
1154
            if (!$shop) {
1155
                continue;
1156
            }
1157
1158
            $this->productTranslationsGateway->addArticleTranslation($translation, $article->getId(), $shop->getId());
1159
        }
1160
    }
1161
1162
    /**
1163
     * dsadsa
1164
     * @return \Shopware\Components\Model\ModelRepository
1165
     */
1166
    private function getLocaleRepository()
1167
    {
1168
        if (!$this->localeRepository) {
1169
            $this->localeRepository = $this->manager->getRepository('Shopware\Models\Shop\Locale');
1170
        }
1171
1172
        return $this->localeRepository;
1173
    }
1174
1175
    private function getShopRepository()
1176
    {
1177
        if (!$this->shopRepository) {
1178
            $this->shopRepository = $this->manager->getRepository('Shopware\Models\Shop\Shop');
1179
        }
1180
1181
        return $this->shopRepository;
1182
    }
1183
1184
    /**
1185
     * @param Product $product
1186
     * @return ProductStream
1187
     */
1188
    private function getOrCreateStream(Product $product)
1189
    {
1190
        /** @var ProductStreamRepository $repo */
1191
        $repo = $this->manager->getRepository(ProductStreamAttribute::class);
1192
        $stream = $repo->findConnectByName($product->stream);
1193
1194
        if (!$stream) {
1195
            $stream = new ProductStream();
1196
            $stream->setName($product->stream);
1197
            $stream->setType(ProductStreamService::STATIC_STREAM);
1198
            $stream->setSorting(json_encode(
1199
                [ReleaseDateSorting::class => ['direction' => 'desc']]
1200
            ));
1201
1202
            //add attributes
1203
            $attribute = new \Shopware\Models\Attribute\ProductStream();
1204
            $attribute->setProductStream($stream);
1205
            $attribute->setConnectIsRemote(true);
1206
            $stream->setAttribute($attribute);
1207
1208
            $this->manager->persist($attribute);
1209
            $this->manager->persist($stream);
1210
            $this->manager->flush();
1211
        }
1212
1213
        return $stream;
1214
    }
1215
1216
    /**
1217
     * @param ProductStream $stream
1218
     * @param ProductModel $article
1219
     * @throws \Doctrine\DBAL\DBALException
1220
     */
1221
    private function addProductToStream(ProductStream $stream, ProductModel $article)
1222
    {
1223
        $conn = $this->manager->getConnection();
1224
        $sql = 'INSERT INTO `s_product_streams_selection` (`stream_id`, `article_id`)
1225
                VALUES (:streamId, :articleId)
1226
                ON DUPLICATE KEY UPDATE stream_id = :streamId, article_id = :articleId';
1227
        $stmt = $conn->prepare($sql);
1228
        $stmt->execute([':streamId' => $stream->getId(), ':articleId' => $article->getId()]);
1229
    }
1230
1231
    /**
1232
     * Delete product or product variant with given shopId and sourceId.
1233
     *
1234
     * Only the combination of both identifies a product uniquely. Do NOT
1235
     * delete products just by their sourceId.
1236
     *
1237
     * You might receive delete requests for products, which are not available
1238
     * in your shop. Just ignore them.
1239
     *
1240
     * @param string $shopId
1241
     * @param string $sourceId
1242
     * @return void
1243
     */
1244
    public function delete($shopId, $sourceId)
1245
    {
1246
        $detail = $this->helper->getArticleDetailModelByProduct(new Product([
1247
            'shopId' => $shopId,
1248
            'sourceId' => $sourceId,
1249
        ]));
1250
        if ($detail === null) {
1251
            return;
1252
        }
1253
1254
        $this->deleteDetail($detail);
1255
    }
1256
1257
    public function update($shopId, $sourceId, ProductUpdate $product)
1258
    {
1259
        // find article detail id
1260
        $articleDetailId = $this->manager->getConnection()->fetchColumn(
1261
            'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?',
1262
            [$sourceId, $shopId]
1263
        );
1264
1265
        $this->eventManager->notify(
1266
            'Connect_Merchant_Update_GeneralProductInformation',
1267
            [
1268
                'subject' => $this,
1269
                'shopId' => $shopId,
1270
                'sourceId' => $sourceId,
1271
                'articleDetailId' => $articleDetailId
1272
            ]
1273
        );
1274
1275
        // update purchasePriceHash, offerValidUntil and purchasePrice in connect attribute
1276
        $this->manager->getConnection()->executeUpdate(
1277
            'UPDATE s_plugin_connect_items SET purchase_price_hash = ?, offer_valid_until = ?, purchase_price = ?
1278
            WHERE source_id = ? AND shop_id = ?',
1279
            [
1280
                $product->purchasePriceHash,
1281
                $product->offerValidUntil,
1282
                $product->purchasePrice,
1283
                $sourceId,
1284
                $shopId,
1285
            ]
1286
        );
1287
1288
        // update stock in article detail
1289
        // update prices
1290
        // if purchase price is stored in article detail
1291
        // update it together with stock
1292
        // since shopware 5.2
1293
        if (method_exists('Shopware\Models\Article\Detail', 'getPurchasePrice')) {
1294
            $this->manager->getConnection()->executeUpdate(
1295
                'UPDATE s_articles_details SET instock = ?, purchaseprice = ? WHERE id = ?',
1296
                [$product->availability, $product->purchasePrice, $articleDetailId]
1297
            );
1298
        } else {
1299
            $this->manager->getConnection()->executeUpdate(
1300
                'UPDATE s_articles_details SET instock = ? WHERE id = ?',
1301
                [$product->availability, $articleDetailId]
1302
            );
1303
        }
1304
        $this->manager->getConnection()->executeUpdate(
1305
            "UPDATE s_articles_prices SET price = ?, baseprice = ? WHERE articledetailsID = ? AND pricegroup = 'EK'",
1306
            [$product->price, $product->purchasePrice, $articleDetailId]
1307
        );
1308
    }
1309
1310
    public function changeAvailability($shopId, $sourceId, $availability)
1311
    {
1312
        // find article detail id
1313
        $articleDetailId = $this->manager->getConnection()->fetchColumn(
1314
            'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?',
1315
            [$sourceId, $shopId]
1316
        );
1317
1318
        $this->eventManager->notify(
1319
            'Connect_Merchant_Update_GeneralProductInformation',
1320
            [
1321
                'subject' => $this,
1322
                'shopId' => $shopId,
1323
                'sourceId' => $sourceId,
1324
                'articleDetailId' => $articleDetailId
1325
            ]
1326
        );
1327
1328
        // update stock in article detail
1329
        $this->manager->getConnection()->executeUpdate(
1330
            'UPDATE s_articles_details SET instock = ? WHERE id = ?',
1331
            [$availability, $articleDetailId]
1332
        );
1333
    }
1334
1335
    /**
1336
     * @inheritDoc
1337
     */
1338
    public function makeMainVariant($shopId, $sourceId, $groupId)
1339
    {
1340
        //find article detail which should be selected as main one
1341
        $newMainDetail = $this->helper->getConnectArticleDetailModel($sourceId, $shopId);
1342
        if (!$newMainDetail) {
1343
            return;
1344
        }
1345
1346
        /** @var \Shopware\Models\Article\Article $article */
1347
        $article = $newMainDetail->getArticle();
1348
1349
        $this->eventManager->notify(
1350
            'Connect_Merchant_Update_ProductMainVariant_Before',
1351
            [
1352
                'subject' => $this,
1353
                'shopId' => $shopId,
1354
                'sourceId' => $sourceId,
1355
                'articleId' => $article->getId(),
1356
                'articleDetailId' => $newMainDetail->getId()
1357
            ]
1358
        );
1359
1360
        // replace current main detail with new one
1361
        $currentMainDetail = $article->getMainDetail();
1362
        $currentMainDetail->setKind(2);
1363
        $newMainDetail->setKind(1);
1364
        $article->setMainDetail($newMainDetail);
1365
1366
        $this->manager->persist($newMainDetail);
1367
        $this->manager->persist($currentMainDetail);
1368
        $this->manager->persist($article);
1369
        $this->manager->flush();
1370
    }
1371
}
1372