Completed
Pull Request — master (#330)
by Stefan
03:38
created

Article::regenerateChangesForArticle()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
rs 8.439
cc 6
eloc 17
nc 6
nop 1
1
<?php
2
3
namespace ShopwarePlugins\Connect\Subscribers;
4
use Shopware\CustomModels\Connect\Attribute;
5
use ShopwarePlugins\Connect\Components\Config;
6
use Shopware\Connect\Struct\Change\FromShop\MakeMainVariant;
7
use Shopware\Models\Customer\Group;
8
use Shopware\Connect\Gateway;
9
use Shopware\Components\Model\ModelManager;
10
use ShopwarePlugins\Connect\Components\ConnectExport;
11
use Shopware\Models\Article\Article as ArticleModel;
12
use ShopwarePlugins\Connect\Components\Helper;
13
14
/**
15
 * Class Article
16
 * @package ShopwarePlugins\Connect\Subscribers
17
 */
18
class Article extends BaseSubscriber
19
{
20
    /**
21
     * @var \Shopware\Connect\Gateway\PDO
22
     */
23
    private $connectGateway;
24
25
    /**
26
     * @var \Shopware\Components\Model\ModelManager
27
     */
28
    private $modelManager;
29
30
    /**
31
     * @var \Shopware\Models\Customer\Group
32
     */
33
    private $customerGroupRepository;
34
35
    /**
36
     * @var \Shopware\Models\Article\Detail
37
     */
38
    private $detailRepository;
39
40
    /**
41
     * @var \ShopwarePlugins\Connect\Components\ConnectExport
42
     */
43
    private $connectExport;
44
45
    /**
46
     * @var Helper
47
     */
48
    private $helper;
49
50
    /**
51
     * @var Config
52
     */
53
    private $config;
54
55
    public function __construct(
56
        Gateway $connectGateway,
57
        ModelManager $modelManager,
58
        ConnectExport $connectExport,
59
        Helper $helper,
60
        Config $config
61
    ) {
62
        parent::__construct();
63
        $this->connectGateway = $connectGateway;
0 ignored issues
show
Documentation Bug introduced by
$connectGateway is of type object<Shopware\Connect\Gateway>, but the property $connectGateway was declared to be of type object<Shopware\Connect\Gateway\PDO>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
64
        $this->modelManager = $modelManager;
65
        $this->connectExport = $connectExport;
66
        $this->helper = $helper;
67
        $this->config = $config;
68
    }
69
70
    public function getSubscribedEvents()
71
    {
72
        return array(
73
            'Shopware_Controllers_Backend_Article::preparePricesAssociatedData::after' => 'enforceConnectPriceWhenSaving',
74
            'Enlight_Controller_Action_PostDispatch_Backend_Article' => 'extendBackendArticle',
75
            'Enlight_Controller_Action_PreDispatch_Backend_Article' => 'preBackendArticle',
76
            'Enlight_Controller_Action_PostDispatch_Frontend_Detail' => 'modifyConnectArticle',
77
            'Enlight_Controller_Action_PreDispatch_Frontend_Detail' => 'extendFrontendArticle'
78
        );
79
    }
80
81
    /**
82
     * @return \Shopware\Models\Article\Detail
83
     */
84
    public function getDetailRepository()
85
    {
86
        if (!$this->detailRepository) {
87
            $this->detailRepository = $this->modelManager->getRepository('Shopware\Models\Article\Detail');
88
        }
89
90
        return $this->detailRepository;
91
    }
92
93
    /**
94
     * @return \Shopware\Components\Model\ModelRepository|Group
95
     */
96
    public function getCustomerGroupRepository()
97
    {
98
        if (!$this->customerGroupRepository) {
99
            $this->customerGroupRepository = $this->modelManager->getRepository('Shopware\Models\Customer\Group');
100
        }
101
        return $this->customerGroupRepository;
102
    }
103
104
    /**
105
     * @param \Enlight_Event_EventArgs $args
106
     */
107
    public function preBackendArticle(\Enlight_Event_EventArgs $args)
108
    {
109
        /** @var $subject \Enlight_Controller_Action */
110
        $subject = $args->getSubject();
111
        $request = $subject->Request();
112
113
        switch ($request->getActionName()) {
114
            case 'saveDetail':
115
                if ($request->getParam('standard')) {
116
                    $this->generateMainVariantChange($request->getParam('id'));
117
                }
118
                break;
119
            case 'createConfiguratorVariants':
120
                if (!$articleId = $request->getParam('articleId')) {
121
                    return;
122
                }
123
124
                $this->deleteVariants($articleId);
125
                break;
126
        }
127
    }
128
129
    /**
130
     * @event Enlight_Controller_Action_PostDispatch_Backend_Article
131
     * @param \Enlight_Event_EventArgs $args
132
     */
133
    public function extendBackendArticle(\Enlight_Event_EventArgs $args)
134
    {
135
        /** @var $subject \Enlight_Controller_Action */
136
        $subject = $args->getSubject();
137
        $request = $subject->Request();
138
139
        switch ($request->getActionName()) {
140
            case 'index':
141
                $this->registerMyTemplateDir();
142
                $this->registerMySnippets();
143
                $subject->View()->extendsTemplate(
144
                    'backend/article/connect.js'
145
                );
146
                break;
147
            case 'load':
148
                $this->registerMyTemplateDir();
149
                $this->registerMySnippets();
150
                $subject->View()->extendsTemplate(
151
                    'backend/article/model/attribute_connect.js'
152
                );
153
                $subject->View()->assign('disableConnectPrice', 'true');
154
                $subject->View()->extendsTemplate(
155
                    'backend/article/view/detail/connect_tab.js'
156
                );
157
                $subject->View()->extendsTemplate(
158
                    'backend/article/view/detail/prices_connect.js'
159
                );
160
                $subject->View()->extendsTemplate(
161
                    'backend/article/controller/detail_connect.js'
162
                );
163
                $subject->View()->extendsTemplate(
164
                    'backend/article/view/detail/connect_properties.js'
165
                );
166
                break;
167
            case 'setPropertyList':
168
                // property values are saved in different ajax call then
169
                // property group and this will generate wrong Connect changes.
170
                // after the property values are saved, the temporary property group is no needed
171
                // and it will generate right Connect changes
172
                $articleId = $request->getParam('articleId', null);
173
174
                /** @var ArticleModel $article */
175
                $article = $this->modelManager->find(ArticleModel::class, $articleId);
176
177
                if (!$article) {
178
                    return;
179
                }
180
181
                if (!$article->getPropertyGroup()) {
182
                    return;
183
                }
184
185
                // Check if entity is a connect product
186
                $attribute = $this->helper->getConnectAttributeByModel($article);
187
                if (!$attribute) {
188
                    return;
189
                }
190
191
                // if article is not exported to Connect
192
                // don't need to generate changes
193
                if (!$this->helper->isProductExported($attribute) || !empty($attribute->getShopId())) {
194
                    return;
195
                }
196
197
                if (!$this->hasPriceType()) {
198
                    return;
199
                }
200
201
                $detail = $article->getMainDetail();
202
203
                if ($detail->getAttribute()->getConnectPropertyGroup()) {
204
                    $detail->getAttribute()->setConnectPropertyGroup(null);
205
                    $this->modelManager->persist($detail);
206
                    $this->modelManager->flush();
207
                }
208
209
                $sourceIds = Shopware()->Db()->fetchCol(
210
                    'SELECT source_id FROM s_plugin_connect_items WHERE article_id = ?',
211
                    array($article->getId())
212
                );
213
214
                $this->connectExport->export($sourceIds);
215
                break;
216
            case 'createConfiguratorVariants':
217
                // main detail should be updated as well, because shopware won't call lifecycle event
218
                // even postUpdate of Detail. By this way Connect will generate change for main variant,
219
                // otherwise $product->variant property is an empty array
220
                // if main detail is not changed, Connect SDK won't generate change for it.
221
                // ticket CON-3747
222
                if (!$articleId = $request->getParam('articleId')) {
223
                    return;
224
                }
225
226
                $this->regenerateChangesForArticle($articleId);
227
                break;
228
            case 'getPropertyList':
229
                $subject->View()->data = $this->addConnectFlagToProperties(
230
                    $subject->View()->data
231
                );
232
                break;
233
            case 'deleteAllVariants':
234
                if ($articleId = $request->getParam('articleId')) {
235
                    /** @var ArticleModel $article */
236
                    $article = $this->modelManager->find(ArticleModel::class, (int) $articleId);
237
                    if (!$article) {
238
                        return;
239
                    }
240
241
                    $this->deleteVariants($articleId);
242
                }
243
                break;
244
            default:
245
                break;
246
        }
247
    }
248
249
    /**
250
     * @param int $articleId
251
     */
252
    public function regenerateChangesForArticle($articleId)
253
    {
254
        $autoUpdateProducts = $this->config->getConfig('autoUpdateProducts', Config::UPDATE_AUTO);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $autoUpdateProducts is correct as $this->config->getConfig...ts\Config::UPDATE_AUTO) (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...
255
        if ($autoUpdateProducts == Config::UPDATE_MANUAL) {
256
            return;
257
        }
258
259
        /** @var \Shopware\Models\Article\Article $article */
260
        $article = $this->modelManager->getRepository(ArticleModel::class)->find((int)$articleId);
261
        if (!$article) {
262
            return;
263
         }
264
265
        $attribute = $this->helper->getConnectAttributeByModel($article);
266
        if (!$attribute) {
267
            return;
268
        }
269
270
        // Check if entity is a connect product
271
        if (!$this->helper->isProductExported($attribute)) {
272
            return;
273
        }
274
275
        if ($autoUpdateProducts == Config::UPDATE_CRON_JOB) {
276
            $this->connectExport->markArticleForCronUpdate($articleId);
277
            return;
278
        }
279
280
        $sourceIds = $this->helper->getArticleSourceIds([$articleId]);
281
        $this->connectExport->export($sourceIds);
282
    }
283
284
    /**
285
     * Delete all variants of given product except main one
286
     *
287
     * @param int $articleId
288
     */
289
    private function deleteVariants($articleId)
290
    {
291
        $autoUpdateProducts = $this->config->getConfig('autoUpdateProducts', Config::UPDATE_AUTO);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $autoUpdateProducts is correct as $this->config->getConfig...ts\Config::UPDATE_AUTO) (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...
292
        if ($autoUpdateProducts == Config::UPDATE_MANUAL) {
293
            return;
294
        }
295
296
        /** @var \Shopware\Models\Article\Article $article */
297
        $article = $this->modelManager->getRepository(ArticleModel::class)->find((int)$articleId);
298
        if (!$article) {
299
            return;
300
        }
301
302
        $connectAttribute = $this->helper->getConnectAttributeByModel($article);
303
        if (!$connectAttribute) {
304
            return;
305
        }
306
307
        // Check if entity is a connect product
308
        if (!$this->helper->isProductExported($connectAttribute)) {
309
            return;
310
        }
311
312
        $mainVariantSourceId = $connectAttribute->getSourceId();
313
        $sourceIds = array_filter(
314
            $this->helper->getArticleSourceIds([$article->getId()]),
315
            function ($sourceId) use ($mainVariantSourceId) {
316
                return $sourceId != $mainVariantSourceId;
317
            }
318
        );
319
320
        foreach ($sourceIds as $sourceId) {
321
            $this->getSDK()->recordDelete($sourceId);
322
        }
323
324
        $this->connectExport->updateConnectItemsStatus($sourceIds, Attribute::STATUS_DELETE);
325
    }
326
327
    public function addConnectFlagToProperties($data)
328
    {
329
        $groups = [];
330
        foreach ($data as $group) {
331
            $options = [];
332
            foreach ($group['value'] as $value) {
333
                $element = $value;
334
                $optionId = $value['id'];
335
                $valueModel = $this->modelManager->getRepository('Shopware\Models\Property\Value')->find($optionId);
336
337
                $attribute = null;
338
                if ($valueModel) {
339
                    $attribute = $valueModel->getAttribute();
340
                }
341
342
                if ($attribute && $attribute->getConnectIsRemote()) {
343
                    $element['connect'] = true;
344
                } else {
345
                    $element['connect'] = false;
346
                }
347
                $options[] = $element;
348
            }
349
350
            $group['value'] = $options;
351
            $groups[] = $group;
352
        }
353
354
        return $groups;
355
    }
356
357
    /**
358
     * @param $detailId
359
     */
360
    public function generateMainVariantChange($detailId)
361
    {
362
        $detail = $this->getDetailRepository()->findOneBy(array('id' => $detailId));
363
364
        if (!$detail 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...
365
            return;
366
        }
367
368
        //if it is already main variant dont generate MakeMainVariant change
369
        if ($detail->getKind() == 1) {
370
            return;
371
        }
372
373
        $attribute = $this->helper->getConnectAttributeByModel($detail);
374
375
        if (!$attribute) {
376
            return;
377
        }
378
        // Check if entity is a connect product
379
        if (!$this->helper->isProductExported($attribute)) {
380
            return;
381
        }
382
383
        if (!$this->hasPriceType()) {
384
            return;
385
        }
386
387
        $groupId = $attribute->getGroupId() ? $attribute->getGroupId() : $attribute->getArticleId();
388
389
        $mainVariant = new MakeMainVariant(array(
390
            'sourceId' => $attribute->getSourceId(),
391
            'groupId' => $groupId
392
        ));
393
394
        try {
395
            $this->getSDK()->makeMainVariant($mainVariant);
396
        } catch (\Exception $e) {
397
            // if sn is not available, proceed without exception
398
        }
399
    }
400
401
    /**
402
     * When saving prices make sure, that the connectPrice is stored in net
403
     *
404
     * @param \Enlight_Hook_HookArgs $args
405
     */
406
    public function enforceConnectPriceWhenSaving(\Enlight_Hook_HookArgs $args)
407
    {
408
        /** @var array $prices */
409
        $prices = $args->getReturn();
410
411
        $connectCustomerGroup = $this->getConnectCustomerGroup();
412
        if (!$connectCustomerGroup) {
413
            return;
414
        }
415
        $connectCustomerGroupKey = $connectCustomerGroup->getKey();
416
        $defaultPrices = array();
417
        foreach ($prices as $key => $priceData) {
418
            if ($priceData['customerGroupKey'] == $connectCustomerGroupKey) {
419
                return;
420
            }
421
            if ($priceData['customerGroupKey'] == 'EK') {
422
                $defaultPrices[] = $priceData;
423
            }
424
        }
425
426
        foreach ($defaultPrices as $price) {
427
            $prices[] = array(
428
                'from' => $price['from'],
429
                'to' => $price['to'],
430
                'price' => $price['price'],
431
                'pseudoPrice' => $price['pseudoPrice'],
432
                'basePrice' => $price['basePrice'],
433
                'percent' => $price['percent'],
434
                'customerGroup' => $connectCustomerGroup,
435
                'article' => $price['article'],
436
                'articleDetail' => $price['articleDetail'],
437
            );
438
        }
439
440
        $args->setReturn($prices);
441
    }
442
443
    /**
444
     * @return \Shopware\Models\Customer\Group|null
445
     */
446 View Code Duplication
    public function getConnectCustomerGroup()
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...
447
    {
448
        $repo = Shopware()->Models()->getRepository('Shopware\Models\Attribute\CustomerGroup');
449
        /** @var \Shopware\Models\Attribute\CustomerGroup $model */
450
        $model = $repo->findOneBy(array('connectGroup' => true));
451
452
        $customerGroup = null;
0 ignored issues
show
Unused Code introduced by
$customerGroup is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
453
        if ($model && $model->getCustomerGroup()) {
454
            return $model->getCustomerGroup();
455
        }
456
457
        return null;
458
    }
459
460
    /**
461
     * Load article detail
462
     *
463
     * @param \Enlight_Event_EventArgs $args
464
     */
465
    public function extendFrontendArticle(\Enlight_Event_EventArgs $args)
466
    {
467
        /** @var \Enlight_Controller_Request_RequestHttp $request */
468
        $request = $args->getSubject()->Request();
469
        if ($request->getActionName() != 'index') {
470
            return;
471
        }
472
473
        $detailId = (int) $request->sArticleDetail;
474
        if ($detailId === 0) {
475
            return;
476
        }
477
478
        /** @var \Shopware\Models\Article\Detail $detailModel */
479
        $detailModel = Shopware()->Models()->getRepository('Shopware\Models\Article\Detail')->find($detailId);
480
        if (!$detailModel) {
481
            return;
482
        }
483
484
        $params = array();
485
        /** @var \Shopware\Models\Article\Configurator\Option $option */
486
        foreach ($detailModel->getConfiguratorOptions() as $option) {
487
            $groupId = $option->getGroup()->getId();
488
            $params[$groupId] = $option->getId();
489
        }
490
        $request->setPost('group', $params);
491
    }
492
493
    /**
494
     * Should be possible to buy connect products
495
     * when they're not in stock.
496
     * Depends on remote shop configuration.
497
     *
498
     * @param \Enlight_Event_EventArgs $args
499
     */
500
    public function modifyConnectArticle(\Enlight_Event_EventArgs $args)
501
    {
502
        /** @var \Enlight_Controller_Request_RequestHttp $request */
503
        $request = $args->getSubject()->Request();
504
505
        if ($request->getActionName() != 'index') {
506
            return;
507
        }
508
        $subject = $args->getSubject();
509
        $article = $subject->View()->getAssign('sArticle');
510
        if (!$article) {
511
            return;
512
        }
513
514
        // when article stock is greater than 0
515
        // we don't need to modify it.
516
        if ($article['instock'] > 0) {
517
            return;
518
        }
519
520
        $articleId = $article['articleID'];
521
        $remoteShopId = $this->getRemoteShopId($articleId);
522
        if (!$remoteShopId) {
523
            // article is not imported via Connect
524
            return;
525
        }
526
527
        /** @var \Shopware\Models\Article\Article $articleModel */
528
        $articleModel = Shopware()->Models()->getRepository('Shopware\Models\Article\Article')->find($articleId);
529
        if (!$articleModel) {
530
            return;
531
        }
532
533
        $shopConfiguration = $this->connectGateway->getShopConfiguration($remoteShopId);
0 ignored issues
show
Documentation introduced by
$remoteShopId is of type boolean|integer, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
534
        if ($shopConfiguration->sellNotInStock && !$articleModel->getLastStock()) {
535
            // if selNotInStock is = true and article getLastStock = false
536
            // we don't need to modify it
537
            return;
538
        }
539
540
        if (!$shopConfiguration->sellNotInStock && $articleModel->getLastStock()) {
541
            // if sellNotInStock is = false and article getLastStock = true
542
            // we don't need to modify it
543
            return;
544
        }
545
546
        // sellNotInStock is opposite on articleLastStock
547
        // when it's true, lastStock must be false
548
        $articleModel->setLastStock(!$shopConfiguration->sellNotInStock);
549
        Shopware()->Models()->persist($articleModel);
550
        Shopware()->Models()->flush();
551
552
        // modify assigned article
553
        if ($shopConfiguration->sellNotInStock) {
554
            $article['laststock'] = false;
555
            $article['instock'] = 100;
556
            $article['isAvailable'] = true;
557
        } else {
558
            $article['laststock'] = true;
559
        }
560
        $subject->View()->assign('sArticle', $article);
561
    }
562
563
    /**
564
     * Not using the default helper-methods here, in order to keep this small and without any dependencies
565
     * to the SDK
566
     *
567
     * @param $id
568
     * @return boolean|int
569
     */
570
    private function getRemoteShopId($id)
571
    {
572
        $sql = 'SELECT shop_id FROM s_plugin_connect_items WHERE article_id = ? AND shop_id IS NOT NULL';
573
        return Shopware()->Db()->fetchOne($sql, array($id));
574
    }
575
}