Lifecycle::onDeleteDetail()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 18
rs 9.6666
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\Subscribers;
9
10
use Enlight\Event\SubscriberInterface;
11
use Shopware\Connect\SDK;
12
use Shopware\Connect\Struct\PaymentStatus;
13
use Shopware\Components\Model\ModelManager;
14
use Shopware\Connect\Struct\Tracking;
15
use Shopware\CustomModels\Connect\Attribute;
16
use ShopwarePlugins\Connect\Components\Config;
17
use ShopwarePlugins\Connect\Components\Helper;
18
use ShopwarePlugins\Connect\Components\Utils;
19
use ShopwarePlugins\Connect\Components\ConnectExport;
20
use Shopware\Models\Order\Order;
21
use Symfony\Component\Config\Definition\Exception\Exception;
22
23
/**
24
 * Handles article lifecycle events in order to automatically update/delete products to/from connect
25
 *
26
 * Class Lifecycle
27
 * @package ShopwarePlugins\Connect\Subscribers
28
 */
29
class Lifecycle implements SubscriberInterface
30
{
31
    /**
32
     * @var \Shopware\Components\Model\ModelManager
33
     */
34
    private $manager;
35
36
    /**
37
     * @var Helper
38
     */
39
    private $helper;
40
    /**
41
     * @var SDK
42
     */
43
    private $sdk;
44
45
    /**
46
     * @var Config
47
     */
48
    private $config;
49
50
    /**
51
     * @var ConnectExport
52
     */
53
    private $connectExport;
54
55
    /**
56
     * @var int
57
     */
58
    private $autoUpdateProducts;
59
60
    /**
61
     * Lifecycle constructor.
62
     *
63
     * @param ModelManager $modelManager
64
     * @param Helper $helper
65
     * @param SDK $sdk
66
     * @param Config $config
67
     * @param ConnectExport $connectExport
68
     */
69
    public function __construct(
70
        ModelManager $modelManager,
71
        Helper $helper,
72
        SDK $sdk,
73
        Config $config,
74
        ConnectExport $connectExport
75
    ) {
76
        $this->manager = $modelManager;
77
        $this->helper = $helper;
78
        $this->sdk = $sdk;
79
        $this->config = $config;
80
        $this->connectExport = $connectExport;
81
        $this->autoUpdateProducts = $this->config->getConfig('autoUpdateProducts', 1);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->autoUpdateProducts is correct as $this->config->getConfig('autoUpdateProducts', 1) (which targets ShopwarePlugins\Connect\...nts\Config::getConfig()) 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...
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public static function getSubscribedEvents()
88
    {
89
        return [
90
            'Shopware\Models\Article\Article::preUpdate' => 'onPreUpdate',
91
            'Shopware\Models\Article\Article::postPersist' => 'onUpdateArticle',
92
            'Shopware\Models\Article\Detail::postPersist' => 'onPersistDetail',
93
            'Shopware\Models\Article\Article::preRemove' => 'onDeleteArticle',
94
            'Shopware\Models\Article\Detail::preRemove' => 'onDeleteDetail',
95
            'Shopware\Models\Order\Order::postUpdate' => 'onUpdateOrder',
96
            'Shopware\Models\Shop\Shop::preRemove' => 'onDeleteShop',
97
            'Shopware\Models\Category\Category::preRemove' => 'onPreDeleteCategory',
98
            'Shopware\Models\Category\Category::postRemove' => 'onPostDeleteCategory'
99
        ];
100
    }
101
102
    public function onPreUpdate(\Enlight_Event_EventArgs $eventArgs)
103
    {
104
        /** @var \Shopware\Models\Article\Article $entity */
105
        $entity = $eventArgs->get('entity');
106
        $db = Shopware()->Db();
107
108
        // Check if entity is a connect product
109
        $attribute = $this->helper->getConnectAttributeByModel($entity);
110
        if (!$attribute) {
111
            return;
112
        }
113
114
        // if article is not exported to Connect
115
        // don't need to generate changes
116
        if (!$this->helper->isProductExported($attribute) || !empty($attribute->getShopId())) {
117
            return;
118
        }
119
120
        if (!SDK::isPriceTypeValid($this->sdk->getPriceType())) {
121
            return;
122
        }
123
124
        $changeSet = $eventArgs->get('entityManager')->getUnitOfWork()->getEntityChangeSet($entity);
125
126
        // If product propertyGroup is changed we need to store the old one,
127
        // because product property value are still not changed and
128
        // this will generate wrong Connect changes
129
        if ($changeSet['propertyGroup']) {
130
            $filterGroupId = $db->fetchOne(
131
                'SELECT filtergroupID FROM s_articles WHERE id = ?',
132
                [$entity->getId()]
133
            );
134
135
            $db->executeUpdate(
136
                'UPDATE `s_articles_attributes` SET `connect_property_group` = ? WHERE `articledetailsID` = ?',
137
                [$filterGroupId, $entity->getMainDetail()->getId()]
138
            );
139
        }
140
    }
141
142
    /**
143
     * @param \Enlight_Event_EventArgs $eventArgs
144
     */
145
    public function onUpdateOrder(\Enlight_Event_EventArgs $eventArgs)
146
    {
147
        /** @var \Shopware\Models\Order\Order $order */
148
        $order = $eventArgs->get('entity');
149
150
        // Compute the changeset and return, if orderStatus did not change
151
        $changeSet = $eventArgs->get('entityManager')->getUnitOfWork()->getEntityChangeSet($order);
152
153
        if (isset($changeSet['paymentStatus'])) {
154
            $this->updatePaymentStatus($order);
155
        }
156
157
        if (isset($changeSet['orderStatus']) || isset($changeSet['trackingCode'])) {
158
            $this->updateOrderStatus($order);
159
        }
160
    }
161
162
    /**
163
     * Callback function to delete an product from connect
164
     * after it is going to be deleted locally
165
     *
166
     * @param \Enlight_Event_EventArgs $eventArgs
167
     */
168
    public function onDeleteArticle(\Enlight_Event_EventArgs $eventArgs)
169
    {
170
        $entity = $eventArgs->get('entity');
171
        $this->connectExport->setDeleteStatusForVariants($entity);
172
    }
173
174
    /**
175
     * Callback function to delete an product from connect
176
     * after it is going to be deleted locally
177
     *
178
     * @param \Enlight_Event_EventArgs $eventArgs
179
     */
180
    public function onPreDeleteCategory(\Enlight_Event_EventArgs $eventArgs)
181
    {
182
        if ($this->autoUpdateProducts===Config::UPDATE_MANUAL) {
183
            return;
184
        }
185
        $category = $eventArgs->get('entity');
186
        $this->connectExport->markProductsInToBeDeletedCategories($category);
187
    }
188
189
    /**
190
     * Callback function to delete an product from connect
191
     * after it is going to be deleted locally
192
     *
193
     * @param \Enlight_Event_EventArgs $eventArgs
194
     */
195
    public function onPostDeleteCategory(\Enlight_Event_EventArgs $eventArgs)
196
    {
197
        // if update is set to auto we have to export all marked products
198
        // else cron_job will do it or user does it manually
199
        if ($this->autoUpdateProducts===Config::UPDATE_AUTO) {
200
            $this->connectExport->handleMarkedProducts();
201
        }
202
    }
203
204
    /**
205
     * Callback function to delete product detail from connect
206
     * after it is going to be deleted locally
207
     *
208
     * @param \Enlight_Event_EventArgs $eventArgs
209
     */
210
    public function onDeleteDetail(\Enlight_Event_EventArgs $eventArgs)
211
    {
212
        /** @var \Shopware\Models\Article\Detail $entity */
213
        $entity = $eventArgs->get('entity');
214
        if ($entity->getKind() !== 1) {
215
            $attribute = $this->helper->getConnectAttributeByModel($entity);
216
217
            if (!$attribute) {
218
                return;
219
            }
220
221
            if (!$this->helper->isProductExported($attribute)) {
222
                return;
223
            }
224
            $this->sdk->recordDelete($attribute->getSourceId());
225
            $this->connectExport->updateConnectItemsStatus([$attribute->getSourceId()], Attribute::STATUS_DELETE);
226
        }
227
    }
228
229
    /**
230
     * Callback method to update changed connect products
231
     *
232
     * @param \Enlight_Event_EventArgs $eventArgs
233
     */
234
    public function onUpdateArticle(\Enlight_Event_EventArgs $eventArgs)
235
    {
236
        $entity = $eventArgs->get('entity');
237
238
        $this->handleChange($entity);
239
    }
240
241
    /**
242
     * Generate changes for Article or Detail if necessary
243
     *
244
     * @param \Shopware\Models\Article\Article | \Shopware\Models\Article\Detail $entity
245
     */
246
    public function handleChange($entity)
247
    {
248
        if (!$entity 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...
249
            && !$entity instanceof \Shopware\Models\Article\Detail
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Detail 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...
250
        ) {
251
            return;
252
        }
253
254
        $id = $entity->getId();
255
        $className = get_class($entity);
256
        $model = $this->manager->getRepository($className)->find($id);
257
        // Check if we have a valid model
258
        if (!$model) {
259
            return;
260
        }
261
262
        // Check if entity is a connect product
263
        $attribute = $this->helper->getConnectAttributeByModel($model);
264
        if (!$attribute) {
265
            return;
266
        }
267
268
        // if article is not exported to Connect
269
        // or at least one article detail from same article is not exported
270
        // don't need to generate changes
271
        if (!$this->helper->isProductExported($attribute) || !empty($attribute->getShopId())) {
272
            if (!$this->helper->hasExportedVariants($attribute)) {
273
                return;
274
            }
275
        }
276
277
        if (!SDK::isPriceTypeValid($this->sdk->getPriceType())) {
278
            return;
279
        }
280
281
        $forceExport = false;
282
        if ($entity instanceof \Shopware\Models\Article\Detail) {
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Detail 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...
283
            $changeSet = $this->manager->getUnitOfWork()->getEntityChangeSet($entity);
284
            // if detail number has been changed
285
            // sc plugin must generate & sync the change immediately
286
            if (array_key_exists('number', $changeSet)) {
287
                $forceExport = true;
288
            }
289
        }
290
291
        // Mark the product for connect update
292
        try {
293
            if ($model instanceof \Shopware\Models\Article\Detail) {
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Detail 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...
294
                $this->generateChangesForDetail($model, $forceExport);
295
            } elseif ($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...
296
                $this->generateChangesForArticle($model, $forceExport);
297
            }
298
        } catch (\Exception $e) {
299
            // If the update fails due to missing requirements
300
            // (e.g. category assignment), continue without error
301
        }
302
    }
303
304
    /**
305
     * Callback method to insert new article details in Connect system
306
     * Used when article is exported and after that variants are generated
307
     *
308
     * @param \Enlight_Event_EventArgs $eventArgs
309
     */
310
    public function onPersistDetail(\Enlight_Event_EventArgs $eventArgs)
311
    {
312
        /** @var \Shopware\Models\Article\Detail $detail */
313
        $detail = $eventArgs->get('entity');
314
315
        /** @var \Shopware\Models\Article\Article $article */
316
        $article = $detail->getArticle();
317
        $articleAttribute = $this->helper->getConnectAttributeByModel($article);
318
        if (!$articleAttribute) {
319
            return;
320
        }
321
322
        // if article is not exported to Connect
323
        // don't need to generate changes
324
        if (!$this->helper->isProductExported($articleAttribute) || !empty($articleAttribute->getShopId())) {
325
            return;
326
        }
327
328
        if (!SDK::isPriceTypeValid($this->sdk->getPriceType())) {
329
            return;
330
        }
331
332
        // Mark the article detail for connect export
333
        try {
334
            $attribute = $this->helper->getOrCreateConnectAttributeByModel($detail);
335
            $forceExport = false;
336
            $changeSet = $eventArgs->get('entityManager')->getUnitOfWork()->getEntityChangeSet($detail);
337
            // if detail number has been changed
338
            // sc plugin must generate & sync the change immediately
339
            if (array_key_exists('number', $changeSet)) {
340
                $forceExport = true;
341
            }
342
343
            // if the variant is assigned to a new Article we have to delete the old variant
344
            // and insert a new ConnectAttribute with the right articleId
345
            if (array_key_exists('articleId', $changeSet) && $attribute->getArticleId() !== $detail->getArticleId()) {
346
                $this->sdk->recordDelete($attribute->getSourceId());
347
                $this->manager->delete($attribute);
348
                $this->manager->flush();
349
350
                $this->helper->getOrCreateConnectAttributeByModel($detail);
351
            }
352
353
            $this->generateChangesForDetail($detail, $forceExport);
354
        } catch (\Exception $e) {
355
            // If the update fails due to missing requirements
356
            // (e.g. category assignment), continue without error
357
        }
358
    }
359
360
    /**
361
     * Callback function to shop from export languages
362
     *
363
     * @param \Enlight_Event_EventArgs $eventArgs
364
     */
365
    public function onDeleteShop(\Enlight_Event_EventArgs $eventArgs)
366
    {
367
        /** @var \Shopware\Models\Shop\Shop $shop */
368
        $shop = $eventArgs->get('entity');
369
        $shopId = $shop->getId();
370
371
        $exportLanguages = $this->config->getConfig('exportLanguages');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $exportLanguages is correct as $this->config->getConfig('exportLanguages') (which targets ShopwarePlugins\Connect\...nts\Config::getConfig()) 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...
372
        $exportLanguages = $exportLanguages ?: [];
373
374
        $shopIndex = array_search($shopId, $exportLanguages);
375
        if (false === $shopIndex) {
376
            return;
377
        }
378
379
        array_splice($exportLanguages, $shopIndex, 1);
380
        $this->config->setConfig('exportLanguages', $exportLanguages, null, 'export');
381
    }
382
383
    /**
384
     * @param \Shopware\Models\Article\Detail $detail
385
     * @param bool $force
386
     */
387 View Code Duplication
    private function generateChangesForDetail(\Shopware\Models\Article\Detail $detail, $force = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
388
    {
389
        $attribute = $this->helper->getConnectAttributeByModel($detail);
390
        if (!$detail->getActive() && $this->config->getConfig('excludeInactiveProducts')) {
391
            $this->connectExport->syncDeleteDetail($detail);
392
393
            return;
394
        }
395
396
        if ($this->isAutoUpdateEnabled($force)) {
397
            $this->connectExport->export(
398
                [$attribute->getSourceId()],
399
                null
400
            );
401
        } elseif ($this->autoUpdateProducts == Config::UPDATE_CRON_JOB) {
402
            $this->manager->getConnection()->update(
403
                's_plugin_connect_items',
404
                ['cron_update' => 1],
405
                ['article_detail_id' => $detail->getId()]
406
            );
407
        }
408
    }
409
410
    /**
411
     * @param \Shopware\Models\Article\Article $article
412
     * @param bool $force
413
     */
414 View Code Duplication
    private function generateChangesForArticle(\Shopware\Models\Article\Article $article, $force = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
415
    {
416
        if (!$article->getActive() && $this->config->getConfig('excludeInactiveProducts')) {
417
            $this->connectExport->setDeleteStatusForVariants($article);
418
419
            return;
420
        }
421
422
        if ($this->isAutoUpdateEnabled($force)) {
423
            $sourceIds = $this->helper->getSourceIdsFromArticleId($article->getId());
424
425
            $this->connectExport->export($sourceIds, null);
426
        } elseif ($this->autoUpdateProducts == Config::UPDATE_CRON_JOB) {
427
            $this->manager->getConnection()->update(
428
                's_plugin_connect_items',
429
                ['cron_update' => 1],
430
                ['article_id' => $article->getId()]
431
            );
432
        }
433
    }
434
435
    /**
436
     * @param bool $force
437
     * @return bool
438
     */
439
    private function isAutoUpdateEnabled($force)
440
    {
441
        return $this->autoUpdateProducts == Config::UPDATE_AUTO || $force === true;
442
    }
443
444
    /**
445
     * Sends the new order status when supplier change it
446
     *
447
     * @param Order $order
448
     */
449
    private function updateOrderStatus(Order $order)
450
    {
451
        $attribute = $order->getAttribute();
452
        if (!$attribute || !$attribute->getConnectShopId()) {
453
            return;
454
        }
455
456
        $orderStatusMapper = new Utils\OrderStatusMapper();
457
        $orderStatus = $orderStatusMapper->getOrderStatusStructFromOrder($order);
458
        $tracking = new Tracking();
459
        $tracking->id = $order->getTrackingCode();
460
        $orderStatus->tracking = $tracking;
461
        try {
462
            $this->sdk->updateOrderStatus($orderStatus);
463
        } catch (\Exception $e) {
464
            // if sn is not available, proceed without exception
465
        }
466
    }
467
468
    /**
469
     * Sends the new payment status when merchant change it
470
     *
471
     * @param Order $order
472
     */
473
    private function updatePaymentStatus(Order $order)
474
    {
475
        $orderUtil = new Utils\ConnectOrderUtil();
476
        if (!$orderUtil->hasLocalOrderConnectProducts($order->getId())) {
477
            return;
478
        }
479
480
        $paymentStatusMapper = new Utils\OrderPaymentStatusMapper();
481
        $paymentStatus = $paymentStatusMapper->getPaymentStatus($order);
482
483
        $this->generateChangeForPaymentStatus($paymentStatus);
484
    }
485
486
    /**
487
     * @param PaymentStatus $paymentStatus
488
     */
489
    private function generateChangeForPaymentStatus(PaymentStatus $paymentStatus)
490
    {
491
        try {
492
            $this->sdk->updatePaymentStatus($paymentStatus);
493
        } catch (\Exception $e) {
494
            // if sn is not available, proceed without exception
495
        }
496
    }
497
}
498