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

Article::generateMainVariantChange()   C

Complexity

Conditions 8
Paths 9

Size

Total Lines 40
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 40
rs 5.3846
c 1
b 0
f 0
cc 8
eloc 20
nc 9
nop 1
1
<?php
2
3
namespace ShopwarePlugins\Connect\Subscribers;
4
use ShopwarePlugins\Connect\Components\Config;
5
use Shopware\Connect\Struct\Change\FromShop\MakeMainVariant;
6
use Shopware\Models\Customer\Group;
7
use Shopware\Connect\Gateway;
8
use Shopware\Components\Model\ModelManager;
9
use ShopwarePlugins\Connect\Components\ConnectExport;
10
use Shopware\Models\Article\Article as ArticleModel;
11
use ShopwarePlugins\Connect\Components\Helper;
12
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamsAssignments;
13
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamService;
14
15
/**
16
 * Class Article
17
 * @package ShopwarePlugins\Connect\Subscribers
18
 */
19
class Article extends BaseSubscriber
20
{
21
    /**
22
     * @var \Shopware\Connect\Gateway\PDO
23
     */
24
    private $connectGateway;
25
26
    /**
27
     * @var \Shopware\Components\Model\ModelManager
28
     */
29
    private $modelManager;
30
31
    /**
32
     * @var \Shopware\Models\Customer\Group
33
     */
34
    private $customerGroupRepository;
35
36
    /**
37
     * @var \Shopware\Models\Article\Detail
38
     */
39
    private $detailRepository;
40
41
    /**
42
     * @var \ShopwarePlugins\Connect\Components\ConnectExport
43
     */
44
    private $connectExport;
45
46
    /**
47
     * @var ProductStreamService
48
     */
49
    private $productStreamService;
50
51
    /**
52
     * @var Helper
53
     */
54
    private $helper;
55
56
    /**
57
     * @var Config
58
     */
59
    private $config;
60
61
    public function __construct(
62
        Gateway $connectGateway,
63
        ModelManager $modelManager,
64
        ConnectExport $connectExport,
65
        ProductStreamService $productStreamService,
66
        Helper $helper,
67
        Config $config
68
    ) {
69
        parent::__construct();
70
        $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...
71
        $this->modelManager = $modelManager;
72
        $this->connectExport = $connectExport;
73
        $this->productStreamService = $productStreamService;
74
        $this->helper = $helper;
75
        $this->config = $config;
76
    }
77
78
    public function getSubscribedEvents()
79
    {
80
        return array(
81
            'Shopware_Controllers_Backend_Article::preparePricesAssociatedData::after' => 'enforceConnectPriceWhenSaving',
82
            'Enlight_Controller_Action_PostDispatch_Backend_Article' => 'extendBackendArticle',
83
            'Enlight_Controller_Action_PreDispatch_Backend_Article' => 'preBackendArticle',
84
            'Enlight_Controller_Action_PostDispatch_Frontend_Detail' => 'modifyConnectArticle',
85
            'Enlight_Controller_Action_PreDispatch_Frontend_Detail' => 'extendFrontendArticle'
86
        );
87
    }
88
89
    /**
90
     * @return \Shopware\Models\Article\Detail
91
     */
92
    public function getDetailRepository()
93
    {
94
        if (!$this->detailRepository) {
95
            $this->detailRepository = $this->modelManager->getRepository('Shopware\Models\Article\Detail');
96
        }
97
98
        return $this->detailRepository;
99
    }
100
101
    /**
102
     * @return \Shopware\Components\Model\ModelRepository|Group
103
     */
104
    public function getCustomerGroupRepository()
105
    {
106
        if (!$this->customerGroupRepository) {
107
            $this->customerGroupRepository = $this->modelManager->getRepository('Shopware\Models\Customer\Group');
108
        }
109
        return $this->customerGroupRepository;
110
    }
111
112
    /**
113
     * @param \Enlight_Event_EventArgs $args
114
     */
115
    public function preBackendArticle(\Enlight_Event_EventArgs $args)
116
    {
117
        /** @var $subject \Enlight_Controller_Action */
118
        $subject = $args->getSubject();
119
        $request = $subject->Request();
120
121
        switch ($request->getActionName()) {
122
            case 'saveDetail':
123
                if ($request->getParam('standard')) {
124
                    $this->generateMainVariantChange($request->getParam('id'));
125
                }
126
                break;
127
        }
128
    }
129
130
    /**
131
     * @event Enlight_Controller_Action_PostDispatch_Backend_Article
132
     * @param \Enlight_Event_EventArgs $args
133
     */
134
    public function extendBackendArticle(\Enlight_Event_EventArgs $args)
135
    {
136
        /** @var $subject \Enlight_Controller_Action */
137
        $subject = $args->getSubject();
138
        $request = $subject->Request();
139
140
        switch ($request->getActionName()) {
141
            case 'index':
142
                $this->registerMyTemplateDir();
143
                $this->registerMySnippets();
144
                $subject->View()->extendsTemplate(
145
                    'backend/article/connect.js'
146
                );
147
                break;
148
            case 'load':
149
                $this->registerMyTemplateDir();
150
                $this->registerMySnippets();
151
                $subject->View()->extendsTemplate(
152
                    'backend/article/model/attribute_connect.js'
153
                );
154
155
//                if (\Shopware::VERSION != '__VERSION__' && version_compare(\Shopware::VERSION, '4.2.2', '<')) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
156
                $subject->View()->assign('disableConnectPrice', 'true');
157
//
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
158
//                    $subject->View()->extendsTemplate(
159
//                        'backend/article/model/price_attribute_connect.js'
160
//                    );
161
//                }
162
163
                $subject->View()->extendsTemplate(
164
                    'backend/article/view/detail/connect_tab.js'
165
                );
166
                $subject->View()->extendsTemplate(
167
                    'backend/article/view/detail/prices_connect.js'
168
                );
169
                $subject->View()->extendsTemplate(
170
                    'backend/article/controller/detail_connect.js'
171
                );
172
                $subject->View()->extendsTemplate(
173
                    'backend/article/view/detail/connect_properties.js'
174
                );
175
                break;
176
            case 'setPropertyList':
177
                // property values are saved in different ajax call then
178
                // property group and this will generate wrong Connect changes.
179
                // after the property values are saved, the temporary property group is no needed
180
                // and it will generate right Connect changes
181
                $articleId = $request->getParam('articleId', null);
182
183
                /** @var ArticleModel $article */
184
                $article = $this->modelManager->find(ArticleModel::class, $articleId);
185
186
                if (!$article) {
187
                    return;
188
                }
189
190
                if (!$article->getPropertyGroup()) {
191
                    return;
192
                }
193
194
                // Check if entity is a connect product
195
                $attribute = $this->helper->getConnectAttributeByModel($article);
196
                if (!$attribute) {
197
                    return;
198
                }
199
200
                // if article is not exported to Connect
201
                // don't need to generate changes
202
                if (!$this->helper->isProductExported($attribute) || !empty($attribute->getShopId())) {
203
                    return;
204
                }
205
206
                if (!$this->hasPriceType()) {
207
                    return;
208
                }
209
210
                $detail = $article->getMainDetail();
211
212
                if ($detail->getAttribute()->getConnectPropertyGroup()) {
213
                    $detail->getAttribute()->setConnectPropertyGroup(null);
214
                    $this->modelManager->persist($detail);
215
                    $this->modelManager->flush();
216
                }
217
218
                $sourceIds = Shopware()->Db()->fetchCol(
219
                    'SELECT source_id FROM s_plugin_connect_items WHERE article_id = ?',
220
                    array($article->getId())
221
                );
222
223
                $this->connectExport->export($sourceIds);
224
                break;
225
            case 'createConfiguratorVariants':
226
                // main detail should be updated as well, because shopware won't call lifecycle event
227
                // even postUpdate of Detail. By this way Connect will generate change for main variant,
228
                // otherwise $product->variant property is an empty array
229
                // if main detail is not changed, Connect SDK won't generate change for it.
230
                // ticket CON-3747
231
                if (!$articleId = $request->getParam('articleId')) {
232
                    return;
233
                }
234
235
                $this->regenerateChangesForArticle($articleId);
236
                break;
237
            case 'getPropertyList':
238
                $subject->View()->data = $this->addConnectFlagToProperties(
239
                    $subject->View()->data
240
                );
241
                break;
242
            case 'deleteAllVariants':
243
                if ($articleId = $request->getParam('articleId')) {
244
                    /** @var ArticleModel $article */
245
                    $article = $this->modelManager->find(ArticleModel::class, (int) $articleId);
246
                    if (!$article) {
247
                        return;
248
                    }
249
                    $this->deleteAllVariants($articleId);
250
                }
251
                break;
252
            default:
253
                break;
254
        }
255
    }
256
257
    /**
258
     * @param int $articleId
259
     */
260
    public function regenerateChangesForArticle($articleId)
261
    {
262
        /** @var \Shopware\Models\Article\Article $article */
263
        $article = $this->modelManager->getRepository(ArticleModel::class)->find((int)$articleId);
264
        if (!$article) {
265
            return;
266
        }
267
268
        $attribute = $this->helper->getConnectAttributeByModel($article);
269
        if (!$attribute) {
270
            return;
271
        }
272
273
        // Check if entity is a connect product
274
        if (!$this->helper->isProductExported($attribute)) {
275
            return;
276
        }
277
278
        $this->deleteAllVariants($article);
279
280
        if ($this->config->getConfig('autoUpdateProducts', Config::UPDATE_AUTO) == Config::UPDATE_CRON_JOB) {
281
            $this->modelManager->getConnection()->update(
282
                's_plugin_connect_items',
283
                array('cron_update' => 1),
284
                array('article_id' => $articleId)
285
            );
286
            return;
287
        }
288
289
        $sourceIds = $this->helper->getSourceIdsFromArticleId($articleId);
290
        $this->connectExport->export(
291
            $sourceIds,
292
            new ProductStreamsAssignments(
293
                ['assignments' => $this->productStreamService->collectRelatedStreamsAssignments([$articleId])]
294
            )
295
        );
296
    }
297
298
    /**
299
     * @param ArticleModel $article
300
     */
301
    private function deleteAllVariants(ArticleModel $article)
302
    {
303
        $this->connectExport->setDeleteStatusForVariants($article);
304
    }
305
306
    public function addConnectFlagToProperties($data)
307
    {
308
        $groups = [];
309
        foreach ($data as $group) {
310
            $options = [];
311
            foreach ($group['value'] as $value) {
312
                $element = $value;
313
                $optionId = $value['id'];
314
                $valueModel = $this->modelManager->getRepository('Shopware\Models\Property\Value')->find($optionId);
315
316
                $attribute = null;
317
                if ($valueModel) {
318
                    $attribute = $valueModel->getAttribute();
319
                }
320
321
                if ($attribute && $attribute->getConnectIsRemote()) {
322
                    $element['connect'] = true;
323
                } else {
324
                    $element['connect'] = false;
325
                }
326
                $options[] = $element;
327
            }
328
329
            $group['value'] = $options;
330
            $groups[] = $group;
331
        }
332
333
        return $groups;
334
    }
335
336
    /**
337
     * @param $detailId
338
     */
339
    public function generateMainVariantChange($detailId)
340
    {
341
        $detail = $this->getDetailRepository()->findOneBy(array('id' => $detailId));
342
343
        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...
344
            return;
345
        }
346
347
        //if it is already main variant dont generate MakeMainVariant change
348
        if ($detail->getKind() == 1) {
349
            return;
350
        }
351
352
        $attribute = $this->helper->getConnectAttributeByModel($detail);
353
354
        if (!$attribute) {
355
            return;
356
        }
357
        // Check if entity is a connect product
358
        if (!$this->helper->isProductExported($attribute)) {
359
            return;
360
        }
361
362
        if (!$this->hasPriceType()) {
363
            return;
364
        }
365
366
        $groupId = $attribute->getGroupId() ? $attribute->getGroupId() : $attribute->getArticleId();
367
368
        $mainVariant = new MakeMainVariant(array(
369
            'sourceId' => $attribute->getSourceId(),
370
            'groupId' => $groupId
371
        ));
372
373
        try {
374
            $this->getSDK()->makeMainVariant($mainVariant);
375
        } catch (\Exception $e) {
376
            // if sn is not available, proceed without exception
377
        }
378
    }
379
380
    /**
381
     * When saving prices make sure, that the connectPrice is stored in net
382
     *
383
     * @param \Enlight_Hook_HookArgs $args
384
     */
385
    public function enforceConnectPriceWhenSaving(\Enlight_Hook_HookArgs $args)
386
    {
387
        /** @var array $prices */
388
        $prices = $args->getReturn();
389
390
        $connectCustomerGroup = $this->getConnectCustomerGroup();
391
        if (!$connectCustomerGroup) {
392
            return;
393
        }
394
        $connectCustomerGroupKey = $connectCustomerGroup->getKey();
395
        $defaultPrices = array();
396
        foreach ($prices as $key => $priceData) {
397
            if ($priceData['customerGroupKey'] == $connectCustomerGroupKey) {
398
                return;
399
            }
400
            if ($priceData['customerGroupKey'] == 'EK') {
401
                $defaultPrices[] = $priceData;
402
            }
403
        }
404
405
        foreach ($defaultPrices as $price) {
406
            $prices[] = array(
407
                'from' => $price['from'],
408
                'to' => $price['to'],
409
                'price' => $price['price'],
410
                'pseudoPrice' => $price['pseudoPrice'],
411
                'basePrice' => $price['basePrice'],
412
                'percent' => $price['percent'],
413
                'customerGroup' => $connectCustomerGroup,
414
                'article' => $price['article'],
415
                'articleDetail' => $price['articleDetail'],
416
            );
417
        }
418
419
        $args->setReturn($prices);
420
    }
421
422
    /**
423
     * @return \Shopware\Models\Customer\Group|null
424
     */
425 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...
426
    {
427
        $repo = Shopware()->Models()->getRepository('Shopware\Models\Attribute\CustomerGroup');
428
        /** @var \Shopware\Models\Attribute\CustomerGroup $model */
429
        $model = $repo->findOneBy(array('connectGroup' => true));
430
431
        $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...
432
        if ($model && $model->getCustomerGroup()) {
433
            return $model->getCustomerGroup();
434
        }
435
436
        return null;
437
    }
438
439
    /**
440
     * Load article detail
441
     *
442
     * @param \Enlight_Event_EventArgs $args
443
     */
444
    public function extendFrontendArticle(\Enlight_Event_EventArgs $args)
445
    {
446
        /** @var \Enlight_Controller_Request_RequestHttp $request */
447
        $request = $args->getSubject()->Request();
448
        if ($request->getActionName() != 'index') {
449
            return;
450
        }
451
452
        $detailId = (int) $request->sArticleDetail;
453
        if ($detailId === 0) {
454
            return;
455
        }
456
457
        /** @var \Shopware\Models\Article\Detail $detailModel */
458
        $detailModel = Shopware()->Models()->getRepository('Shopware\Models\Article\Detail')->find($detailId);
459
        if (!$detailModel) {
460
            return;
461
        }
462
463
        $params = array();
464
        /** @var \Shopware\Models\Article\Configurator\Option $option */
465
        foreach ($detailModel->getConfiguratorOptions() as $option) {
466
            $groupId = $option->getGroup()->getId();
467
            $params[$groupId] = $option->getId();
468
        }
469
        $request->setPost('group', $params);
470
    }
471
472
    /**
473
     * Should be possible to buy connect products
474
     * when they're not in stock.
475
     * Depends on remote shop configuration.
476
     *
477
     * @param \Enlight_Event_EventArgs $args
478
     */
479
    public function modifyConnectArticle(\Enlight_Event_EventArgs $args)
480
    {
481
        /** @var \Enlight_Controller_Request_RequestHttp $request */
482
        $request = $args->getSubject()->Request();
483
484
        if ($request->getActionName() != 'index') {
485
            return;
486
        }
487
        $subject = $args->getSubject();
488
        $article = $subject->View()->getAssign('sArticle');
489
        if (!$article) {
490
            return;
491
        }
492
493
        // when article stock is greater than 0
494
        // we don't need to modify it.
495
        if ($article['instock'] > 0) {
496
            return;
497
        }
498
499
        $articleId = $article['articleID'];
500
        $remoteShopId = $this->getRemoteShopId($articleId);
501
        if (!$remoteShopId) {
502
            // article is not imported via Connect
503
            return;
504
        }
505
506
        /** @var \Shopware\Models\Article\Article $articleModel */
507
        $articleModel = Shopware()->Models()->getRepository('Shopware\Models\Article\Article')->find($articleId);
508
        if (!$articleModel) {
509
            return;
510
        }
511
512
        $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...
513
        if ($shopConfiguration->sellNotInStock && !$articleModel->getLastStock()) {
514
            // if selNotInStock is = true and article getLastStock = false
515
            // we don't need to modify it
516
            return;
517
        }
518
519
        if (!$shopConfiguration->sellNotInStock && $articleModel->getLastStock()) {
520
            // if sellNotInStock is = false and article getLastStock = true
521
            // we don't need to modify it
522
            return;
523
        }
524
525
        // sellNotInStock is opposite on articleLastStock
526
        // when it's true, lastStock must be false
527
        $articleModel->setLastStock(!$shopConfiguration->sellNotInStock);
528
        Shopware()->Models()->persist($articleModel);
529
        Shopware()->Models()->flush();
530
531
        // modify assigned article
532
        if ($shopConfiguration->sellNotInStock) {
533
            $article['laststock'] = false;
534
            $article['instock'] = 100;
535
            $article['isAvailable'] = true;
536
        } else {
537
            $article['laststock'] = true;
538
        }
539
        $subject->View()->assign('sArticle', $article);
540
    }
541
542
    /**
543
     * Not using the default helper-methods here, in order to keep this small and without any dependencies
544
     * to the SDK
545
     *
546
     * @param $id
547
     * @return boolean|int
548
     */
549
    private function getRemoteShopId($id)
550
    {
551
        $sql = 'SELECT shop_id FROM s_plugin_connect_items WHERE article_id = ? AND shop_id IS NOT NULL';
552
        return Shopware()->Db()->fetchOne($sql, array($id));
553
    }
554
}