Completed
Pull Request — master (#393)
by Jonas
03:07
created

Lifecycle::getSubscribedEvents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
354
            );
355
        } elseif ($this->autoUpdateProducts == 2) {
356
            $this->manager->getConnection()->update(
357
                's_plugin_connect_items',
358
                ['cron_update' => 1],
359
                ['article_detail_id' => $detail->getId()]
360
            );
361
        }
362
    }
363
364 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...
365
    {
366
        if (!$article->getActive() && $this->getConnectConfig()->getConfig('excludeInactiveProducts')) {
367
            $this->getConnectExport()->setDeleteStatusForVariants($article);
368
369
            return;
370
        }
371
372
        if ($this->autoUpdateProducts == 1 || $force === true) {
373
            $sourceIds = $this->getHelper()->getSourceIdsFromArticleId($article->getId());
374
375
            $this->getConnectExport()->export($sourceIds, null, true);
0 ignored issues
show
Unused Code introduced by
The call to ConnectExport::export() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
376
        } elseif ($this->autoUpdateProducts == 2) {
377
            $this->manager->getConnection()->update(
378
                's_plugin_connect_items',
379
                ['cron_update' => 1],
380
                ['article_id' => $article->getId()]
381
            );
382
        }
383
    }
384
385
    /**
386
     * Sends the new order status when supplier change it
387
     *
388
     * @param Order $order
389
     */
390
    private function updateOrderStatus(Order $order)
391
    {
392
        $attribute = $order->getAttribute();
393
        if (!$attribute || !$attribute->getConnectShopId()) {
394
            return;
395
        }
396
397
        $orderStatusMapper = new Utils\OrderStatusMapper();
398
        $orderStatus = $orderStatusMapper->getOrderStatusStructFromOrder($order);
399
400
        try {
401
            $this->getSDK()->updateOrderStatus($orderStatus);
402
        } catch (\Exception $e) {
403
            // if sn is not available, proceed without exception
404
        }
405
    }
406
407
    /**
408
     * Sends the new payment status when merchant change it
409
     *
410
     * @param Order $order
411
     */
412
    private function updatePaymentStatus(Order $order)
413
    {
414
        $orderUtil = new Utils\ConnectOrderUtil();
415
        if (!$orderUtil->hasLocalOrderConnectProducts($order->getId())) {
416
            return;
417
        }
418
419
        $paymentStatusMapper = new Utils\OrderPaymentStatusMapper();
420
        $paymentStatus = $paymentStatusMapper->getPaymentStatus($order);
421
422
        $this->generateChangeForPaymentStatus($paymentStatus);
423
    }
424
425
    /**
426
     * @param PaymentStatus $paymentStatus
427
     */
428
    private function generateChangeForPaymentStatus(PaymentStatus $paymentStatus)
429
    {
430
        try {
431
            $this->getSDK()->updatePaymentStatus($paymentStatus);
432
        } catch (\Exception $e) {
433
            // if sn is not available, proceed without exception
434
        }
435
    }
436
}
437