Completed
Pull Request — master (#333)
by Simon
04:01
created

applyMappingToChildrenAction()   A

Complexity

Conditions 2
Paths 5

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 15
nc 5
nop 0
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * (c) shopware AG <[email protected]>
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
use Shopware\Components\CSRFWhitelistAware;
9
use Shopware\Components\Model\ModelManager;
10
use Shopware\CustomModels\Connect\Attribute;
11
use Shopware\Models\Article\Repository as ArticleRepository;
12
use Shopware\Models\Category\Category;
13
use ShopwarePlugins\Connect\Components\Config;
14
use ShopwarePlugins\Connect\Components\ConnectExport;
15
use ShopwarePlugins\Connect\Components\ConnectFactory;
16
use ShopwarePlugins\Connect\Components\ErrorHandler;
17
use Shopware\Models\Article\Article;
18
use ShopwarePlugins\Connect\Components\Validator\ProductAttributesValidator\ProductsAttributesValidator;
19
use ShopwarePlugins\Connect\Components\Marketplace\MarketplaceSettingsApplier;
20
use ShopwarePlugins\Connect\Components\Marketplace\MarketplaceSettings;
21
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamService;
22
use ShopwarePlugins\Connect\Components\SnHttpClient;
23
use ShopwarePlugins\Connect\Struct\SearchCriteria;
24
use ShopwarePlugins\Connect\Subscribers\Connect;
25
use Shopware\Connect\SDK;
26
27
class Shopware_Controllers_Backend_Connect extends \Shopware_Controllers_Backend_ExtJs implements CSRFWhitelistAware
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
28
{
29
    /**
30
     * @var ConnectFactory
31
     */
32
    private $factory;
33
34
    /**
35
     * @var Config
36
     */
37
    private $configComponent;
38
39
    /**
40
     * @var MarketplaceSettingsApplier
41
     */
42
    private $marketplaceSettingsApplier;
43
44
    /**
45
     * @var \Shopware\Connect\SDK
46
     */
47
    private $sdk;
48
49
    /**
50
     * @var SnHttpClient
51
     */
52
    private $snHttpClient;
53
54
    /**
55
     * @var ProductStreamService
56
     */
57
    private $productStreamService;
58
59
    /**
60
     * @return ModelManager
61
     */
62
    public function getModelManager()
63
    {
64
        return $this->get('models');
65
    }
66
67
    /**
68
     * @return \Shopware\Connect\SDK
69
     */
70
    public function getSDK()
71
    {
72
        if ($this->sdk === null) {
73
            $this->sdk = $this->get('ConnectSDK');
74
        }
75
76
        return $this->sdk;
77
    }
78
79
    /**
80
     * @return \ShopwarePlugins\Connect\Components\Helper
81
     */
82
    public function getHelper()
83
    {
84
        return $this->getConnectFactory()->getHelper();
85
    }
86
87
    /**
88
     * @return ConnectFactory
89
     */
90
    public function getConnectFactory()
91
    {
92
        if ($this->factory === null) {
93
            $this->factory = new ConnectFactory();
94
        }
95
96
        return $this->factory;
97
    }
98
99
    /**
100
     * @return ArticleRepository
101
     */
102
    private function getArticleRepository()
103
    {
104
        return $this->getModelManager()->getRepository(
105
            Article::class
106
        );
107
    }
108
109
    /**
110
     * @return \Shopware\Models\Category\Repository
111
     */
112
    private function getCategoryRepository()
113
    {
114
        return $this->getModelManager()->getRepository(
115
            Category::class
116
        );
117
    }
118
119
    /**
120
     * Will return a category model for the given id. If the attribute should not exist
121
     * it will be created
122
     *
123
     * @param $id
124
     * @return null|Category
125
     */
126
    private function getCategoryModelById($id)
127
    {
128
        $categoryModel = $this->getCategoryRepository()->find($id);
129
        if (!$categoryModel || !$categoryModel->getAttribute()) {
130
            $attribute = new \Shopware\Models\Attribute\Category();
131
            $attribute->setCategory($categoryModel);
132
            $this->getModelManager()->persist($attribute);
133
            $this->getModelManager()->flush($attribute);
134
        }
135
136
        return $categoryModel;
137
    }
138
139
    /**
140
     * When the backend module is being loaded, update connect products.
141
     *
142
     * It might be considerable to move this to e.g. the lifecycle events of the products
143
     */
144
    public function indexAction()
145
    {
146
        $this->getHelper()->updateConnectProducts();
147
148
        parent::loadAction();
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (loadAction() instead of indexAction()). Are you sure this is correct? If so, you might want to change this to $this->loadAction().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
149
    }
150
151
    /**
152
     * When the backend module is being loaded, update connect products.
153
     *
154
     * It might be considerable to move this to e.g. the lifecycle events of the products
155
     */
156
    public function refreshConnectItemsAction()
157
    {
158
        $this->getHelper()->updateConnectProducts();
159
    }
160
161
    /**
162
     * If the price type is purchase or both
163
     * and shopware is 5.2 or greater
164
     * insert detailPurchasePrice in connect config table
165
     * when priceFieldForPurchasePriceExport is empty
166
     */
167
    private function updatePurchasePriceField()
168
    {
169
        $field = $this->getConfigComponent()->getConfig('priceFieldForPurchasePriceExport');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $field is correct as $this->getConfigComponen...orPurchasePriceExport') (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...
170
        if ($field) {
171
            return;
172
        }
173
174
        if (!method_exists('Shopware\Models\Article\Detail', 'setPurchasePrice')) {
175
            return;
176
        }
177
178
        if ($this->getSDK()->getPriceType() == \Shopware\Connect\SDK::PRICE_TYPE_PURCHASE
179
            || $this->getSDK()->getPriceType() == \Shopware\Connect\SDK::PRICE_TYPE_BOTH
180
        ) {
181
            $this->getConfigComponent()->setConfig(
182
                'priceFieldForPurchasePriceExport',
183
                'detailPurchasePrice',
184
                null,
185
                'export'
186
            );
187
        }
188
    }
189
190
    /**
191
     * Helper function to return a QueryBuilder for creating the listing queries for the import and export listings
192
     *
193
     * @param $filter
194
     * @param $order
195
     * @return \Doctrine\ORM\QueryBuilder
196
     */
197
    private function getListQueryBuilder($filter, $order)
198
    {
199
        $builder = $this->getModelManager()->createQueryBuilder();
200
        $builder->from('Shopware\CustomModels\Connect\Attribute', 'at');
201
        $builder->join('at.article', 'a');
202
        $builder->join('a.mainDetail', 'd');
203
        $builder->leftJoin('d.prices', 'p', 'with', "p.from = 1 AND p.customerGroupKey = 'EK'");
204
        $builder->leftJoin('a.supplier', 's');
205
        $builder->leftJoin('a.tax', 't');
206
207
        $builder->select([
208
            'a.id',
209
            'd.number as number',
210
            'd.inStock as inStock',
211
            'a.name as name',
212
            's.name as supplier',
213
            'a.active as active',
214
            't.tax as tax',
215
            'p.price * (100 + t.tax) / 100 as price',
216
            'at.category'
217
        ]);
218
219
        // show only main variant in export/import lists
220
        $builder->groupBy('at.articleId');
221
222
        foreach ($filter as $key => $rule) {
223
            switch ($rule['property']) {
224
                case 'search':
225
                    $builder->andWhere('d.number LIKE :search OR a.name LIKE :search OR s.name LIKE :search')
226
                        ->setParameter('search', $rule['value']);
227
                    break;
228
                case 'categoryId':
229
                    $builder->join('a.categories', 'c', 'with', 'c.id = :categoryId OR c.path LIKE :categoryPath')
230
                        ->setParameter('categoryId', $rule['value'])
231
                        ->setParameter('categoryPath', '%|' . $rule['value'] . '|%');
232
                    break;
233
                case 'supplierId':
234
                    $builder->andWhere('a.supplierId = :supplierId')
235
                        ->setParameter('supplierId', $rule['value']);
236
                    break;
237
                case 'exportStatus':
238
                    $builder->andWhere('at.exportStatus LIKE :status')
239
                        ->setParameter('status', $rule['value']);
240
                    break;
241
                case 'active':
242
                    $builder->andWhere('a.active LIKE :active')
243
                        ->setParameter('active', $rule['value']);
244
                    break;
245
                default:
246
                    continue;
247
            }
248
        }
249
250
        $builder->addOrderBy($order);
251
252
        return $builder;
253
    }
254
255
    /**
256
     * Get all products exported to connect
257
     */
258
    public function getExportListAction()
259
    {
260
        $filter = (array) $this->Request()->getParam('filter', []);
261
        $order = reset($this->Request()->getParam('sort', []));
0 ignored issues
show
Bug introduced by
$this->Request()->getParam('sort', array()) cannot be passed to reset() as the parameter $array expects a reference.
Loading history...
262
263
        $criteria = new SearchCriteria([
264
            'offset' => (int) $this->Request()->getParam('start'),
265
            'limit' => (int) $this->Request()->getParam('limit'),
266
            'orderBy' => $order['property'],
267
            'orderByDirection' => $order['direction'],
268
269
        ]);
270
271
        foreach ($filter as $key => $rule) {
272
            $field = $rule['property'];
273
            $criteria->{$field} = $rule['value'];
274
        }
275
276
        $exportList = $this->getConnectExport()->getExportList($criteria);
277
278
        $this->View()->assign([
279
            'success' => true,
280
            'data' => $exportList->articles,
281
            'total' => $exportList->count,
282
        ]);
283
    }
284
285
    public function getExportStatusAction()
286
    {
287
        $attrRepo = $this->getModelManager()->getRepository('Shopware\CustomModels\Connect\Attribute');
288
289
        $syncedItems = $attrRepo->countStatus([
290
            Attribute::STATUS_SYNCED,
291
        ]);
292
293
        $totalItems = $attrRepo->countStatus([
294
            Attribute::STATUS_INSERT,
295
            Attribute::STATUS_UPDATE,
296
            Attribute::STATUS_SYNCED,
297
        ]);
298
299
        $this->View()->assign([
300
            'success' => true,
301
            'data' => $syncedItems,
302
            'total' => $totalItems,
303
        ]);
304
    }
305
306
    /**
307
     * Get all products imported from connect
308
     */
309
    public function getImportListAction()
310
    {
311
        $filter = (array) $this->Request()->getParam('filter', []);
312
        $sort = $this->Request()->getParam('sort', []);
313
314
        foreach ($sort as $key => $currentSorter) {
315
            if ($currentSorter['property'] == 'category') {
316
                unset($sort[$key]);
317
            }
318
        }
319
320
        $builder = $this->getListQueryBuilder(
321
            $filter,
322
            $sort
323
        );
324
        $builder->addSelect([
325
            'at.shopId',
326
            'at.sourceId',
327
            'at.exportStatus as connectStatus',
328
        ]);
329
        $builder->andWhere('at.shopId IS NOT NULL');
330
331
        $builder->addOrderBy('at.category', 'ASC');
332
333
        $query = $builder->getQuery();
334
335
        $query->setFirstResult($this->Request()->getParam('start'));
336
        $query->setMaxResults($this->Request()->getParam('limit'));
337
338
        $countResult = array_map('current', $builder->select(['COUNT(DISTINCT at.articleId) as current'])->orderBy('current')->getQuery()->getScalarResult());
339
        $total = array_sum($countResult);
0 ignored issues
show
Unused Code introduced by
$total 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...
340
        $total = array_sum($countResult);
341
        // todo@sb: find better solution. getQueryCount method counts s_plugin_connect_items.id like they are not grouped by article id
342
//        $total = Shopware()->Models()->getQueryCount($query);
343
        $data = $query->getArrayResult();
344
345
        $this->View()->assign([
346
            'success' => true,
347
            'data' => $data,
348
            'total' => $total
349
        ]);
350
    }
351
352
    /**
353
     * Import parts of the connect category tree to shopware
354
     */
355
    public function importConnectCategoriesAction()
356
    {
357
        $fromCategory = $this->Request()->getParam('fromCategory');
358
        $toCategory = $this->Request()->getParam('toCategory');
359
360
        $entityManager = $this->getModelManager();
361
        $helper = $this->getHelper();
0 ignored issues
show
Unused Code introduced by
$helper 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...
362
363
        // Make sure that the target category exists
364
        $toCategoryModel = $this->getCategoryRepository()->find($toCategory);
365
        if (!$toCategoryModel) {
366
            throw new \RuntimeException("Category with id  {$toCategory} not found");
367
        }
368
369
        // The user might have changed the mapping without saving and then hit the "importCategories"
370
        // button. So we save the parent category's mapping first
371
        $parentCategory = $this->getCategoryModelById($toCategory);
372
        $parentCategory->getAttribute()->setConnectImportMapping($fromCategory);
373
        $entityManager->flush();
374
375
        try {
376
            $entityManager->getConnection()->beginTransaction();
377
            $this->importConnectCategories($fromCategory, $toCategory);
378
            $entityManager->getConnection()->commit();
379
        } catch (\Exception $e) {
380
            $entityManager->getConnection()->rollback();
381
            throw new \RuntimeException('Could not import categories', 0, $e);
382
        }
383
    }
384
385
    /**
386
     * Will import a connect category tree into shopware.
387
     *
388
     * @param $fromCategory
389
     * @param $toCategory
390
     */
391
    public function importConnectCategories($fromCategory, $toCategory)
392
    {
393
        $categoriesToImport = $this->getFlatConnectCategories($fromCategory);
394
        $toCategoryModel = $this->getCategoryRepository()->find($toCategory);
395
        $entityManager = $this->getModelManager();
396
397
        /*
398
         * The import string allows to identify categories, which have already been imported for
399
         * this exact import. This does not prevent the user from importing the same sub-tree
400
         * into multiple shopware categories. But it does prevent him from importing the same sub-tree
401
         * into the same category multiple times
402
         */
403
        $importString = $fromCategory . '-' . $toCategory;
404
        $currentLevel = 1;
405
        $mappings = [];
406
407
        foreach ($categoriesToImport as $id => $category) {
408
            $name = $category['name'];
409
            $parent = $category['parent'];
410
            $level = $category['level'];
411
412
            // Only flush after the level changed - this speeds up the import
413
            if ($currentLevel != $level) {
414
                Shopware()->Models()->flush();
415
            }
416
            $currentLevel = $level;
417
418
            /** @var Category $parentModel */
419
            if (!$parent) {
420
                // Top category level - use toCategoryModel
421
                $parentModel = $toCategoryModel;
422
            } else {
423
                // Parent was created before and is referenced in $mappings
424
                $parentModel = $mappings[$parent];
425
            }
426
427
            // Check if there is already a category attribute for this import
428
            $categoryAttributes = $entityManager->getRepository('\Shopware\Models\Attribute\Category')->findBy(
429
                ['connectImported' => $importString, 'connectImportMapping' => $id],
430
                null,
431
                1
432
            );
433
434
            if (!empty($categoryAttributes)) {
435
                /** @var \Shopware\Models\Attribute\Category $categoryAttribute */
436
                $categoryAttribute = array_pop($categoryAttributes);
437
                $category = $categoryAttribute->getCategory();
438
            } else {
439
                // Create category and attribute model
440
                $category = new Category();
441
                $category->setName($name);
442
                $category->setParent($parentModel);
443
444
                $attribute = new \Shopware\Models\Attribute\Category();
445
                $attribute->setConnectImportMapping($id);
446
                $attribute->setConnectImported($importString);
447
                $category->setAttribute($attribute);
448
449
                Shopware()->Models()->persist($category);
450
                Shopware()->Models()->persist($attribute);
451
            }
452
453
454
            // Store the new category model in out $mappings array
455
            $mappings[$id] = $category;
456
        }
457
458
        Shopware()->Models()->flush();
459
    }
460
461
    public function initParamsAction()
462
    {
463
        $marketplaceIcon = $this->getConfigComponent()->getConfig('marketplaceIcon', Connect::MARKETPLACE_ICON);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $marketplaceIcon is correct as $this->getConfigComponen...nect::MARKETPLACE_ICON) (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...
464
        $marketplaceName = $this->getConfigComponent()->getConfig('marketplaceName', Connect::MARKETPLACE_NAME);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $marketplaceName is correct as $this->getConfigComponen...nect::MARKETPLACE_NAME) (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...
465
        $marketplaceNetworkUrl = $this->getConfigComponent()->getConfig('marketplaceNetworkUrl', Connect::MARKETPLACE_SOCIAL_NETWORK_URL);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $marketplaceNetworkUrl is correct as $this->getConfigComponen...ACE_SOCIAL_NETWORK_URL) (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...
466
        $defaultMarketplace = $this->getConfigComponent()->getConfig('isDefault', true);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $defaultMarketplace is correct as $this->getConfigComponen...nfig('isDefault', true) (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...
467
        $isFixedPriceAllowed = 0;
468
        $priceType = $this->getSDK()->getPriceType();
469
        if ($priceType === SDK::PRICE_TYPE_BOTH ||
470
            $priceType === SDK::PRICE_TYPE_RETAIL) {
471
            $isFixedPriceAllowed = 1;
472
        }
473
        $marketplaceIncomingIcon = ($marketplaceName == Connect::MARKETPLACE_NAME ? Connect::MARKETPLACE_GREEN_ICON : $marketplaceIcon);
474
        $marketplaceLogo = $this->getConfigComponent()->getConfig('marketplaceLogo', Connect::MARKETPLACE_LOGO);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $marketplaceLogo is correct as $this->getConfigComponen...nect::MARKETPLACE_LOGO) (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...
475
        $purchasePriceInDetail = method_exists('Shopware\Models\Article\Detail', 'setPurchasePrice') ? 1 : 0;
476
477
        $this->View()->assign([
478
            'success' => true,
479
            'data' => [
480
                'marketplaceName' => $marketplaceName,
481
                'marketplaceNetworkUrl' => $marketplaceNetworkUrl,
482
                'marketplaceIcon' => $marketplaceIcon,
483
                'defaultMarketplace' => $defaultMarketplace,
484
                'isFixedPriceAllowed' => $isFixedPriceAllowed,
485
                'marketplaceIncomingIcon' => $marketplaceIncomingIcon,
486
                'marketplaceLogo' => $marketplaceLogo,
487
                'purchasePriceInDetail' => $purchasePriceInDetail,
488
            ]
489
        ]);
490
    }
491
492
    /**
493
     * Returns a flat array of connect categories
494
     *
495
     * @param $rootCategory
496
     * @return array(
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
497
     *      string => array('id' => string, 'name' => string, 'level' => int, 'parent' => string|null)
498
     * )
499
     */
500
    private function getFlatConnectCategories($rootCategory)
501
    {
502
        $sdk = $this->getSDK();
503
        $connectCategories = $sdk->getCategories();
0 ignored issues
show
Bug introduced by
The method getCategories() does not seem to exist on object<Shopware\Connect\SDK>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
504
505
        $categoriesToImport = [];
506
        foreach ($connectCategories as $id => $name) {
507
            // Skip all entries which do not start with the parent or do not have it at all
508
            if (strpos($id, $rootCategory) !== 0) {
509
                continue;
510
            }
511
512
            $level = substr_count(preg_replace("#^{$rootCategory}#", '', $id), '/');
513
514
            // Skip the root category
515
            if ($level == 0) {
516
                continue;
517
            }
518
519
            $categoriesToImport[$id] = [
520
                'id' => $id,
521
                'name' => $name,
522
                'level' => $level,
523
                'parent' => $level == 1 ? null : implode('/', array_slice(explode('/', $id), 0, -1))
524
            ];
525
        }
526
527
        // Sort the categories ascending by their level, so parent categories can be imported first
528
        uasort(
529
            $categoriesToImport,
530
            function ($a, $b) {
531
                $a = $a['level'];
532
                $b = $b['level'];
533
                if ($a == $b) {
534
                    return 0;
535
                }
536
537
                return ($a < $b) ? -1 : 1;
538
            }
539
        );
540
541
        return $categoriesToImport;
542
    }
543
544
    /**
545
     * Save a given mapping of a given category to all subcategories
546
     */
547
    public function applyMappingToChildrenAction()
548
    {
549
        $categoryId = $this->Request()->getParam('category');
550
        $mapping = $this->Request()->getParam('mapping');
551
552
        $entityManager = $this->getModelManager();
553
554
        try {
555
            $entityManager->getConnection()->beginTransaction();
556
            $this->applyMappingToChildren($mapping, $categoryId);
557
            $entityManager->getConnection()->commit();
558
            $this->View()->assign([
559
                'success' => true
560
            ]);
561
        } catch (\Exception $e) {
562
            $entityManager->getConnection()->rollback();
563
            $this->View()->assign([
564
                'message' => $e->getMessage(),
565
                'success' => false
566
            ]);
567
        }
568
    }
569
570
    /**
571
     * Returns success true if user could be logged in, or false if something went wrong
572
     *
573
     * @return array(
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
574
     *      string => bool
575
     * )
576
     */
577
    public function loginAction()
578
    {
579
        /** @var \Shopware\Components\HttpClient\GuzzleHttpClient $client */
580
        $client = $this->get('http_client');
581
582
        $shopwareId = $this->Request()->getParam('shopwareId');
583
        $password = $this->Request()->getParam('password');
584
        $loginUrl = $this->getHost() . '/sdk/pluginCommunication/login';
585
586
        // Try to login into connect
587
        $response = $client->post(
588
            $loginUrl,
589
            [
590
                'content-type' => 'application/x-www-form-urlencoded'
591
            ],
592
            [
593
                'username' => urlencode($shopwareId),
594
                'password' => urlencode($password)
595
            ]
596
        );
597
598
        $responseObject = json_decode($response->getBody());
599
600
        if (!$responseObject->success) {
601
            $message = $responseObject->reason;
602
603
            if ($responseObject->reason == SDK::WRONG_CREDENTIALS_MESSAGE) {
604
                $snippets = Shopware()->Snippets()->getNamespace('backend/connect/view/main');
605
                $message = $snippets->get(
606
                    'error/wrong_credentials',
607
                    SDK::WRONG_CREDENTIALS_MESSAGE,
608
                    true
609
                );
610
            }
611
612
            $this->View()->assign([
613
                'success' => false,
614
                'message' => $message
615
            ]);
616
617
            return;
618
        }
619
620
        try {
621
            // set apiKey in Connect config table
622
            // after that create completely new SDK instance,
623
            // because correct apiKey should be used during creation
624
            $this->getConfigComponent()->setConfig('apiKey', $responseObject->apiKey, null, 'general');
625
            $sdk = $this->getConnectFactory()->createSDK();
626
            $sdk->verifySdk();
627
            $this->getConfigComponent()->setConfig('apiKeyVerified', true);
628
            $this->getConfigComponent()->setConfig('shopwareId', $shopwareId, null, 'general');
629
            $this->removeConnectMenuEntry();
630
            $marketplaceSettings = $sdk->getMarketplaceSettings();
631
            $this->getMarketplaceApplier()->apply(new MarketplaceSettings($marketplaceSettings));
632
        } catch (\Exception $e) {
633
            $this->getConfigComponent()->setConfig('apiKey', null, null, 'general');
634
635
            $this->View()->assign([
636
                'success' => false,
637
                'message' => $e->getMessage()
638
            ]);
639
640
            return;
641
        }
642
643
        $this->View()->assign([
644
            'success' => true
645
        ]);
646
    }
647
648
    /**
649
     * Returns success true if user could be logged in, or false if something went wrong
650
     *
651
     * @return array(
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
652
     *      string => bool
653
     * )
654
     */
655
    public function registerAction()
656
    {
657
        /** @var \Shopware\Components\HttpClient\GuzzleHttpClient $client */
658
        $client = $this->get('http_client');
659
660
        $shopwareId = $this->Request()->getParam('shopwareID');
661
        $password = $this->Request()->getParam('password');
662
        $email = $this->Request()->getParam('email');
663
664
        // Enter the valid production url here
665
        $host = $this->getHost();
666
667
        $loginUrl = $host . '/sdk/pluginCommunication/register';
668
669
        $response = $client->post(
670
            $loginUrl,
671
            [
672
                'content-type' => 'application/x-www-form-urlencoded'
673
            ],
674
            [
675
                'username'  => urlencode($shopwareId),
676
                'password'  => urlencode($password),
677
                'email'     => urlencode($email)
678
            ]
679
        );
680
681
        $responseObject = json_decode($response->getBody());
682
683
        if (!$responseObject->success) {
684
            $this->View()->assign([
685
                'success' => false,
686
                'message' => $responseObject->reason
687
            ]);
688
689
            return;
690
        }
691
692
        try {
693
            // set apiKey in Connect config table
694
            // after that create completely new SDK instance,
695
            // because correct apiKey should be used during creation
696
            $this->getConfigComponent()->setConfig('apiKey', $responseObject->apiKey, null, 'general');
697
            $sdk = $this->getConnectFactory()->createSDK();
698
            $sdk->verifySdk();
699
            $this->getConfigComponent()->setConfig('apiKeyVerified', true);
700
            $this->getConfigComponent()->setConfig('shopwareId', $shopwareId, null, 'general');
701
            $this->removeConnectMenuEntry();
702
            $marketplaceSettings = $sdk->getMarketplaceSettings();
703
            $this->getMarketplaceApplier()->apply(new MarketplaceSettings($marketplaceSettings));
704
        } catch (\Exception $e) {
705
            $this->getConfigComponent()->setConfig('apiKey', null, null, 'general');
706
707
            $this->View()->assign([
708
                'success' => false,
709
                'message' => $e->getMessage()
710
            ]);
711
712
            return;
713
        }
714
715
        $this->View()->assign([
716
            'success' => true
717
        ]);
718
    }
719
720
    /**
721
     * Redirects and auto login to SN system
722
     */
723
    public function autoLoginAction()
724
    {
725
        return $this->redirect('http://' . $this->getHost() . '/login');
726
    }
727
728
    /**
729
     * @param bool $loggedIn
730
     * @throws Zend_Db_Adapter_Exception
731
     */
732
    private function removeConnectMenuEntry($loggedIn = true)
733
    {
734
        /** @var Enlight_Components_Db_Adapter_Pdo_Mysql $db */
735
        $db = Shopware()->Db();
736
737
        $result = $db->fetchAssoc("SELECT id, parent, pluginID FROM s_core_menu WHERE controller = 'connect' AND name = 'Register' ORDER BY id DESC LIMIT 1");
738
        if (empty($result)) {
739
            return;
740
        }
741
        $row = current($result);
742
743
        $db->exec('DELETE FROM s_core_menu WHERE id = ' . $row['id']);
744
745
        $insertSql = "INSERT INTO s_core_menu (
746
            parent,
747
            name,
748
            class,
749
            pluginID,
750
            controller,
751
            action,
752
            onclick,
753
            active
754
          ) VALUES (
755
            '#parent#',
756
            '#name#',
757
            '#class#',
758
            #pluginID#,
759
            '#controller#',
760
            '#action#',
761
            '#onclick#',
762
            1
763
          )";
764
765
        $db->exec(strtr($insertSql, [
766
            '#parent#' => $row['parent'],
767
            '#name#' => 'Import',
768
            '#class#' => 'sc-icon-import',
769
            '#pluginID#' => $row['pluginID'],
770
            '#controller#' => 'Connect',
771
            '#onclick#' => '',
772
            '#action#' => 'Import'
773
        ]));
774
775
        $db->exec(strtr($insertSql, [
776
            '#parent#' => $row['parent'],
777
            '#name#' => 'Export',
778
            '#class#' => 'sc-icon-export',
779
            '#pluginID#' => $row['pluginID'],
780
            '#controller#' => 'Connect',
781
            '#onclick#' => '',
782
            '#action#' => 'Export'
783
        ]));
784
785
        $db->exec(strtr($insertSql, [
786
            '#parent#' => $row['parent'],
787
            '#name#' => 'Settings',
788
            '#class#' => 'sprite-gear',
789
            '#pluginID#' => $row['pluginID'],
790
            '#controller#' => 'Connect',
791
            '#onclick#' => '',
792
            '#action#' => 'Settings'
793
        ]));
794
795
        $db->exec(strtr($insertSql, [
796
            '#parent#' => $row['parent'],
797
            '#name#' => 'OpenConnect',
798
            '#class#' => 'connect-icon',
799
            '#pluginID#' => $row['pluginID'],
800
            '#controller#' => 'Connect',
801
            '#onclick#' => 'window.open("connect/autoLogin")',
802
            '#action#' => 'OpenConnect'
803
        ]));
804
    }
805
806
    /**
807
     * Helper that will assign a given mapping to all children of a given category
808
     *
809
     * @param $mapping string
810
     * @param $categoryId int
811
     * @throws \Exception
812
     */
813
    private function applyMappingToChildren($mapping, $categoryId)
814
    {
815
        $helper = $this->getHelper();
0 ignored issues
show
Unused Code introduced by
$helper 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...
816
        $ids = $this->getChildCategoriesIds($categoryId);
817
        $entityManager = $this->getModelManager();
818
819
820
        if (!$categoryId) {
821
            throw new \RuntimeException("Category '{$categoryId}' not found");
822
        }
823
824
        // First of all try to save the mapping for the parent category. If that fails,
825
        // it mustn't be done for the child categories
826
        $parentCategory = $this->getCategoryModelById($categoryId);
827
        $parentCategory->getAttribute()->setConnectExportMapping($mapping);
828
        $entityManager->flush();
829
830
        // Don't set the children with models in order to speed things up
831
        $builder = $entityManager->createQueryBuilder();
832
        $builder->update('\Shopware\Models\Attribute\Category', 'categoryAttribute')
833
            ->set('categoryAttribute.connectExportMapping', $builder->expr()->literal($mapping))
834
            ->where($builder->expr()->in('categoryAttribute.categoryId', $ids));
835
836
        $builder->getQuery()->execute();
837
    }
838
839
    /**
840
     * Helper function which returns the IDs of the child categories of a given parent category
841
     *
842
     * @param $parentId int
843
     * @return array
844
     */
845
    private function getChildCategoriesIds($parentId)
846
    {
847
        $query = $this->getModelManager()->createQuery('SELECT c.id from Shopware\Models\Category\Category c WHERE c.path LIKE ?1 ');
848
        $query->setParameter(1, ["%|{$parentId}|%"]);
849
        $result = $query->getResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);
850
851
        // Pop IDs from result rows
852
        return array_map(
853
            function ($row) {
854
                return array_pop($row);
855
            },
856
            $result
857
        );
858
    }
859
860
    /**
861
     * Collect all source ids by given article ids
862
     */
863
    public function getArticleSourceIdsAction()
864
    {
865
        try {
866
            $exportAll = (bool) $this->Request()->getPost('exportAll', false);
867
            $articleIds = $this->Request()->getPost('ids', []);
868
869
            if ($exportAll) {
870
                $articleIds = $this->getHelper()->getAllNonConnectArticleIds();
871
            }
872
873
            if (!is_array($articleIds)) {
874
                $articleIds = [$articleIds];
875
            }
876
877
            $sourceIds = $this->getHelper()->getArticleSourceIds($articleIds);
878
879
            $this->View()->assign([
880
                'success' => true,
881
                'sourceIds' => $sourceIds
882
            ]);
883
        } catch (\Exception $e) {
884
            $this->View()->assign([
885
                'success' => false,
886
                'message' => $e->getMessage()
887
            ]);
888
        }
889
    }
890
891
    /**
892
     * Called when a product variants were marked for update in the connect backend module
893
     */
894
    public function insertOrUpdateProductAction()
895
    {
896
        // if priceType comes from SN and shopware version is 5.2
897
        // priceFieldForPurchasePriceExport is empty
898
        // we need to set it because there isn't customer groups
899
        // purchasePrice is stored always in article detail
900
        $this->updatePurchasePriceField();
901
        $sourceIds = $this->Request()->getPost('sourceIds');
902
        $connectExport = $this->getConnectExport();
903
904
        try {
905
            $errors = $connectExport->export($sourceIds);
906
        } catch (\RuntimeException $e) {
907
            $this->View()->assign([
908
                'success' => false,
909
                'messages' => [ErrorHandler::TYPE_DEFAULT_ERROR => [$e->getMessage()]]
910
            ]);
911
912
            return;
913
        }
914
915
        if (!empty($errors)) {
916
            $this->View()->assign([
917
                'success' => false,
918
                'messages' => $errors
919
            ]);
920
921
            return;
922
        }
923
924
        $this->View()->assign([
925
            'success' => true
926
        ]);
927
    }
928
929
    /**
930
     * Delete a product from connect export
931
     */
932
    public function deleteProductAction()
933
    {
934
        $sdk = $this->getSDK();
935
        $ids = $this->Request()->getPost('ids');
936
        foreach ($ids as $id) {
937
            /** @var \Shopware\Models\Article\Article $model */
938
            $model = $this->getConnectExport()->getArticleModelById($id);
939
            if ($model === null) {
940
                continue;
941
            }
942
            /** @var \Shopware\Models\Article\Detail $detail */
943
            foreach ($model->getDetails() as $detail) {
944
                $attribute = $this->getHelper()->getConnectAttributeByModel($detail);
945
                $sdk->recordDelete($attribute->getSourceId());
946
                $attribute->setExportStatus(Attribute::STATUS_DELETE);
947
                $attribute->setExported(false);
948
            }
949
        }
950
        Shopware()->Models()->flush();
951
    }
952
953
    /**
954
     * Verify a given api key against the connect server
955
     */
956
    public function verifyApiKeyAction()
957
    {
958
        $sdk = $this->getSDK();
959
        try {
960
            $key = $this->Request()->getPost('apiKey');
961
            $sdk->verifyKey($key);
962
            $this->View()->assign([
963
                'success' => true
964
            ]);
965
            $this->getConfigComponent()->setConfig('apiKeyVerified', true);
966
            $marketplaceSettings = $sdk->getMarketplaceSettings();
967
            $this->getMarketplaceApplier()->apply(new MarketplaceSettings($marketplaceSettings));
968
        } catch (\Exception $e) {
969
            $this->View()->assign([
970
                'message' => $e->getMessage(),
971
                'success' => false
972
            ]);
973
            $this->getConfigComponent()->setConfig('apiKeyVerified', false);
974
        }
975
976
        $this->getModelManager()->flush();
977
    }
978
979
    /**
980
     * Returns the connectAttribute data for a given articleId
981
     *
982
     * @throws RuntimeException
983
     * @throws Exception
984
     */
985
    public function getConnectDataAction()
986
    {
987
        $articleId = $this->Request()->getParam('articleId');
988
989
        if (!$articleId) {
990
            throw new \Exception('Connect: ArticleId empty');
991
        }
992
993
        /** @var Article $articleModel */
994
        $articleModel = $this->getArticleRepository()->find($articleId);
995
996
        if (!$articleModel) {
997
            throw new \RuntimeException("Could not find model for article with id {$articleId}");
998
        }
999
1000
        $data = $this->getHelper()->getOrCreateConnectAttributes($articleModel);
1001
1002
        $data = $this->getModelManager()->toArray($data);
1003
        if (isset($data['articleId'])) {
1004
            $data = [$data];
1005
        }
1006
1007
        $this->View()->assign([
1008
            'success' => true,
1009
            'data' => $data
1010
        ]);
1011
    }
1012
1013
    /**
1014
     * Save a given connect attribute
1015
     */
1016
    public function saveConnectAttributeAction()
1017
    {
1018
        $data = $this->Request()->getParams();
1019
1020
        /** @var \Shopware\CustomModels\Connect\Attribute $connectAttribute */
1021
        $connectAttribute = $this->getModelManager()->find('Shopware\CustomModels\Connect\Attribute', $data['id']);
1022
        if (!$connectAttribute) {
1023
            throw new \RuntimeException("Could not find connect attribute with id {$data['id']}");
1024
        }
1025
1026
        /** @var \Shopware\Models\Article\Detail $detail */
1027
        foreach ($connectAttribute->getArticle()->getDetails() as $detail) {
1028
            $connectAttribute = $this->getHelper()->getConnectAttributeByModel($detail);
1029
            // Only allow changes in the fixedPrice field if this is a local product
1030
            if (!$connectAttribute->getShopId()) {
1031
                $connectAttribute->setFixedPrice($data['fixedPrice']);
1032
            }
1033
            // Save the update fields
1034
            foreach ($data as $key => $value) {
1035
                if (strpos($key, 'update') === 0) {
1036
                    $setter = 'set' . ucfirst($key);
1037
                    $connectAttribute->$setter($value);
1038
                }
1039
            }
1040
            $this->getModelManager()->persist($connectAttribute);
1041
        }
1042
1043
        $this->getModelManager()->flush();
1044
1045
        $this->View()->assign(['success' => true]);
1046
    }
1047
1048
    /**
1049
     * Saves the changed "connectAllowed" attribute. Saving this attribute should be done
1050
     * by the shipping-module on its own, right now (as of SW 4.2.0) it does not do so.
1051
     *
1052
     * todo: Once the shipping module is fixed, increase the required version of this plugin
1053
     * and remove this and the unnecessary ExtJS extensions
1054
     */
1055
    public function saveShippingAttributeAction()
1056
    {
1057
        $shippingId = $this->Request()->getParam('shippingId');
1058
        $connectAllowed = $this->Request()->getParam('connectAllowed', true);
1059
1060
        if (!$shippingId) {
1061
            return;
1062
        }
1063
1064
        $shippingRepo = $this->getModelManager()->getRepository('\Shopware\Models\Dispatch\Dispatch');
1065
        /** @var \Shopware\Models\Dispatch\Dispatch $shipping */
1066
        $shipping = $shippingRepo->find($shippingId);
1067
1068
        if (!$shipping) {
1069
            return;
1070
        }
1071
1072
        $attribute = $shipping->getAttribute();
1073
1074
        if (!$attribute) {
1075
            $attribute = new \Shopware\Models\Attribute\Dispatch();
1076
            $attribute->setDispatch($shipping);
1077
            $shipping->setAttribute($attribute);
1078
            $this->getModelManager()->persist($attribute);
1079
        }
1080
1081
        $attribute->setConnectAllowed($connectAllowed);
1082
1083
        $this->getModelManager()->flush();
1084
1085
        $this->View()->assign('success', true);
1086
    }
1087
1088
    /**
1089
     * Lists all logs
1090
     */
1091
    public function getLogsAction()
1092
    {
1093
        $params = $this->Request()->getParams();
1094
        $order = $this->Request()->getParam('sort', [['property' => 'time', 'direction' => 'DESC']]);
1095
        $filters = $this->Request()->getParam('filter');
1096
1097
        $commandFilters = [];
1098
        foreach ($params as $key => $param) {
1099
            if (strpos($key, 'commandFilter_') !== false && $param == 'true') {
1100
                $commandFilters[] = str_replace('commandFilter_', '', $key);
1101
            }
1102
        }
1103
1104
        if (empty($commandFilters)) {
1105
            return;
1106
        }
1107
1108
        foreach ($order as &$rule) {
1109
            if ($rule['property'] == 'time') {
1110
                $rule['property'] = 'id';
1111
            }
1112
            $rule['property'] = 'logs.' . $rule['property'];
1113
        }
1114
1115
        $builder = $this->getModelManager()->createQueryBuilder();
1116
        $builder->select('logs');
1117
        $builder->from('Shopware\CustomModels\Connect\Log', 'logs')
1118
            ->addOrderBy($order)
1119
            ->where('logs.command IN (:commandFilter)')
1120
            ->setParameter('commandFilter', $commandFilters);
1121
1122
        foreach ($filters as $filter) {
1123
            switch ($filter['property']) {
1124
                case 'search':
1125
                    $builder->andWhere(
1126
                        'logs.request LIKE :search OR logs.response LIKE :search'
1127
                    );
1128
                    $builder->setParameter('search', $filter['value']);
1129
                    break;
1130
            }
1131
        }
1132
1133
        switch ($this->Request()->getParam('errorFilter', -1)) {
1134
            case 0:
1135
                $builder->andWhere('logs.isError = 1');
1136
                break;
1137
            case 1:
1138
                $builder->andWhere('logs.isError = 0');
1139
                break;
1140
        }
1141
1142
        $query = $builder->getQuery()
1143
            ->setFirstResult($this->Request()->getParam('start', 0))
1144
            ->setMaxResults($this->Request()->getParam('limit', 25));
1145
1146
        $total = Shopware()->Models()->getQueryCount($query);
1147
        $data = $query->getArrayResult();
1148
1149
        $this->View()->assign([
1150
            'success' => true,
1151
            'data' => $data,
1152
            'total' => $total
1153
        ]);
1154
    }
1155
1156
    /**
1157
     * Get a list of log commands
1158
     */
1159
    public function getLogCommandsAction()
1160
    {
1161
        $data = $this->getModelManager()->getConnection()->fetchAll(
1162
            'SELECT DISTINCT `command` FROM `s_plugin_connect_log`'
1163
        );
1164
1165
        $data = array_map(function ($column) {
1166
            return $column['command'];
1167
        }, $data);
1168
1169
        // Enforce these fields
1170
        foreach (['fromShop', 'toShop', 'getLastRevision', 'update', 'checkProducts', 'buy', 'reserveProducts', 'confirm'] as $value) {
1171
            if (!in_array($value, $data)) {
1172
                $data[] = $value;
1173
            }
1174
        }
1175
1176
        $this->View()->assign([
1177
            'success' => true,
1178
            'data' => $data
1179
        ]);
1180
    }
1181
1182
    /**
1183
     * Delete all log entries
1184
     */
1185
    public function clearLogAction()
1186
    {
1187
        $connection = $this->getModelManager()->getConnection();
1188
        $connection->exec('TRUNCATE `s_plugin_connect_log`;');
1189
    }
1190
1191
    /**
1192
     * @return ConnectExport
1193
     */
1194
    public function getConnectExport()
1195
    {
1196
        return new ConnectExport(
1197
            $this->getHelper(),
1198
            $this->getSDK(),
1199
            $this->getModelManager(),
1200
            new ProductsAttributesValidator(),
1201
            $this->getConfigComponent(),
1202
            new ErrorHandler(),
1203
            $this->container->get('events')
1204
        );
1205
    }
1206
1207
    /**
1208
     * @return Config
1209
     */
1210
    public function getConfigComponent()
1211
    {
1212
        if ($this->configComponent === null) {
1213
            $this->configComponent = new Config($this->getModelManager());
1214
        }
1215
1216
        return $this->configComponent;
1217
    }
1218
1219
    /**
1220
     * Saves the dynamic streams to the db.
1221
     * Cronjob will looks for those streams to process them
1222
     */
1223
    public function prepareDynamicStreamsAction()
1224
    {
1225
        $streamIds = $this->Request()->getParam('streamIds', []);
1226
1227
        try {
1228
            $streamService = $this->getProductStreamService();
1229
            $streams = $streamService->findStreams($streamIds);
1230
1231
            if (!$streams) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $streams of type Shopware\Models\ProductStream\ProductStream[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1232
                $message = Shopware()->Snippets()->getNamespace('backend/connect/view/main')->get(
1233
                    'export/message/error_no_stream_selected',
1234
                    'No streams were selected',
1235
                    true
1236
                );
1237
                throw new \Exception($message);
1238
            }
1239
1240
            $modelManager = $this->getModelManager();
1241
1242
            foreach ($streams as $stream) {
1243
                $streamAttr = $streamService->createStreamAttribute($stream);
1244
1245
                if (!$streamAttr->getExportStatus()) {
1246
                    $streamAttr->setExportStatus(ProductStreamService::STATUS_PENDING);
1247
                }
1248
1249
                $modelManager->persist($streamAttr);
1250
            }
1251
1252
            $modelManager->flush();
1253
        } catch (\Exception $e) {
1254
            $this->View()->assign([
1255
                'success' => false,
1256
                'message' => $e->getMessage()
1257
            ]);
1258
        }
1259
1260
        $this->View()->assign([
1261
            'success' => true,
1262
        ]);
1263
    }
1264
1265
    /**
1266
     * Add given category to products
1267
     */
1268
    public function assignProductsToCategoryAction()
1269
    {
1270
        $articleIds = $this->Request()->getParam('ids');
1271
        $categoryId = (int) $this->Request()->getParam('category');
1272
1273
        /** @var Category $category */
1274
        $category = $this->getCategoryModelById($categoryId);
1275
        if (!is_null($category)) {
1276
            foreach ($articleIds as $id) {
1277
                /** @var \Shopware\Models\Article\Article $article */
1278
                $article = $this->getConnectExport()->getArticleModelById($id);
1279
                if (is_null($article)) {
1280
                    continue;
1281
                }
1282
                $article->addCategory($category);
1283
                $this->getModelManager()->persist($article);
1284
            }
1285
            $this->getModelManager()->flush();
1286
        }
1287
1288
        $this->View()->assign(
1289
            [
1290
                'success' => true
1291
            ]
1292
        );
1293
    }
1294
1295
    public function getStreamListAction()
1296
    {
1297
        $productStreamService = $this->getProductStreamService();
1298
1299
        $result = $productStreamService->getList(
1300
            $this->Request()->getParam('start', 0),
1301
            $this->Request()->getParam('limit', 20)
1302
        );
1303
1304
        $this->View()->assign(
1305
            [
1306
                'success' => true,
1307
                'data' => $result['data'],
1308
                'total' => $result['total'],
1309
            ]
1310
        );
1311
    }
1312
1313
    public function getStreamProductsCountAction()
1314
    {
1315
        $streamId = $this->request->getParam('id', null);
1316
1317
        if ($streamId === null) {
1318
            $this->View()->assign([
1319
                'success' => false,
1320
                'message' => 'No stream selected'
1321
            ]);
1322
        }
1323
1324
        $streamsAssignments = $this->getStreamAssignments($streamId);
1325
1326
        if (!$streamsAssignments) {
1327
            return;
1328
        }
1329
1330
        $sourceIds = $this->getHelper()->getArticleSourceIds($streamsAssignments->getArticleIds());
1331
1332
        $this->View()->assign([
1333
            'success' => true,
1334
            'sourceIds' => $sourceIds
1335
        ]);
1336
    }
1337
1338
    public function exportStreamAction()
1339
    {
1340
        $streamIds = $this->request->getParam('streamIds', []);
1341
        $currentStreamIndex = $this->request->getParam('currentStreamIndex', 0);
1342
        $offset = $this->request->getParam('offset', 0);
1343
        $limit = $this->request->getParam('limit', 1);
1344
        $articleDetailIds = $this->request->getParam('articleDetailIds', []);
1345
1346
        $streamId = $streamIds[$currentStreamIndex];
1347
1348
        $productStreamService = $this->getProductStreamService();
1349
1350
        $streamsAssignments = $this->getStreamAssignments($streamId);
1351
1352
        if (!$streamsAssignments) {
1353
            return;
1354
        }
1355
1356
        $sourceIds = $this->getHelper()->getArticleSourceIds($streamsAssignments->getArticleIds());
1357
        $sliced = array_slice($sourceIds, $offset, $limit);
1358
1359
        $exported = $this->exportStreamProducts($streamId, $sliced, $streamsAssignments);
1360
1361
        if (!$exported) {
1362
            return;
1363
        }
1364
1365
        $nextStreamIndex = $currentStreamIndex;
1366
        $newArticleDetailIds = $articleDetailIds;
1367
        $newOffset = $offset + $limit;
1368
        $hasMoreIterations = true;
1369
1370
        $processedStreams = $currentStreamIndex;
1371
1372
        //In this case all the products from a single stream were exported successfully but there are still more streams to be processed.
1373
        if ($newOffset > count($articleDetailIds) && $currentStreamIndex + 1 <= (count($streamIds) - 1)) {
1374
            $productStreamService->changeStatus($streamId, ProductStreamService::STATUS_EXPORT);
1375
            $productStreamService->log($streamId, 'Success');
1376
            $nextStreamIndex = $currentStreamIndex + 1;
1377
1378
            $streamsAssignments = $this->getStreamAssignments($streamIds[$nextStreamIndex]);
1379
1380
            if (!$streamsAssignments) {
1381
                return;
1382
            }
1383
1384
            $sourceIds = $this->getHelper()->getArticleSourceIds($streamsAssignments->getArticleIds());
1385
            $newArticleDetailIds = $sourceIds;
1386
            $newOffset = 0;
1387
        }
1388
1389
        //In this case all the products from all streams were exported successfully.
1390
        if ($newOffset > count($articleDetailIds) && $currentStreamIndex + 1 > (count($streamIds) - 1)) {
1391
            $productStreamService->changeStatus($streamId, ProductStreamService::STATUS_EXPORT);
1392
            $hasMoreIterations = false;
1393
            $newOffset = count($articleDetailIds);
1394
            $processedStreams = count($streamIds);
1395
        }
1396
1397
1398
        $this->View()->assign([
1399
            'success' => true,
1400
            'articleDetailIds' => $newArticleDetailIds,
1401
            'nextStreamIndex' => $nextStreamIndex,
1402
            'newOffset' => $newOffset,
1403
            'hasMoreIterations' => $hasMoreIterations,
1404
            'processedStreams' => $processedStreams,
1405
        ]);
1406
    }
1407
1408
    public function hasManyVariantsAction()
1409
    {
1410
        $streamId = $this->request->getParam('streamId');
1411
        $articleId = $this->request->getParam('articleId');
1412
1413
        if (!$this->getProductStreamService()->isStreamExported($streamId)) {
1414
            return;
1415
        }
1416
1417
        $sourceIds = $this->getHelper()->getSourceIdsFromArticleId($articleId);
1418
1419
        $hasManyVariants = false;
1420
1421
        if (count($sourceIds) > ProductStreamService::PRODUCT_LIMIT) {
1422
            $hasManyVariants = true;
1423
        }
1424
1425
        $this->View()->assign([
1426
            'success' => true,
1427
            'hasManyVariants' => $hasManyVariants
1428
        ]);
1429
    }
1430
1431
    public function exportAllWithCronAction()
1432
    {
1433
        try {
1434
            $db = Shopware()->Db();
1435
            $this->getConfigComponent()->setConfig('autoUpdateProducts', 2, null, 'export');
1436
1437
            $db->update(
1438
                's_crontab',
1439
                ['active' => 1],
1440
                "action = 'ShopwareConnectUpdateProducts' OR action = 'Shopware_CronJob_ShopwareConnectUpdateProducts'"
1441
            );
1442
1443
            $db->update(
1444
                's_plugin_connect_items',
1445
                ['cron_update' => 1, 'export_status' => Attribute::STATUS_UPDATE],
1446
                'shop_id IS NULL'
1447
            );
1448
1449
            $this->View()->assign([
1450
                'success' => true,
1451
            ]);
1452
        } catch (\Exception $e) {
1453
            $this->View()->assign([
1454
                'success' => false,
1455
                'message' => $e->getMessage()
1456
            ]);
1457
        }
1458
    }
1459
1460
    private function getStreamAssignments($streamId)
1461
    {
1462
        $productStreamService = $this->getProductStreamService();
1463
1464
        try {
1465
            $streamsAssignments = $productStreamService->prepareStreamsAssignments($streamId);
1466
        } catch (\Exception $e) {
1467
            $this->View()->assign([
1468
                'success' => false,
1469
                'messages' => [ErrorHandler::TYPE_DEFAULT_ERROR => [$e->getMessage()]]
1470
            ]);
1471
1472
            return false;
1473
        }
1474
1475
        return $streamsAssignments;
1476
    }
1477
1478
    private function exportStreamProducts($streamId, $sourceIds, $streamsAssignments)
1479
    {
1480
        $productStreamService = $this->getProductStreamService();
1481
        $connectExport = $this->getConnectExport();
1482
1483
        try {
1484
            $errorMessages = $connectExport->export($sourceIds, $streamsAssignments);
1485
        } catch (\RuntimeException $e) {
1486
            $productStreamService->changeStatus($streamId, ProductStreamService::STATUS_ERROR);
1487
            $this->View()->assign([
1488
                'success' => false,
1489
                'messages' => [ErrorHandler::TYPE_DEFAULT_ERROR => [$e->getMessage()]]
1490
            ]);
1491
1492
            return false;
1493
        }
1494
1495
        if (!empty($errorMessages)) {
1496
            $productStreamService->changeStatus($streamId, ProductStreamService::STATUS_ERROR);
1497
1498
            $errorMessagesText = '';
1499
            $displayedErrorTypes = [
1500
                ErrorHandler::TYPE_DEFAULT_ERROR,
1501
                ErrorHandler::TYPE_PRICE_ERROR
1502
            ];
1503
1504
            foreach ($displayedErrorTypes as $displayedErrorType) {
1505
                $errorMessagesText .= implode('\n', $errorMessages[$displayedErrorType]);
1506
            }
1507
1508
            $productStreamService->log($streamId, $errorMessagesText);
1509
1510
            $this->View()->assign([
1511
                'success' => false,
1512
                'messages' => $errorMessages
1513
            ]);
1514
1515
            return false;
1516
        }
1517
1518
        return true;
1519
    }
1520
1521
    /**
1522
     * Deletes products from connect stream export
1523
     */
1524
    public function removeStreamsAction()
1525
    {
1526
        $streamIds = $this->request->getParam('ids', []);
1527
1528
        $productStreamService = $this->getProductStreamService();
1529
        $connectExport = $this->getConnectExport();
1530
1531
        $filteredStreamIds = $productStreamService->filterExportedStreams($streamIds);
1532
1533
        foreach ($filteredStreamIds as $streamId) {
1534
            try {
1535
                $removedRecords = [];
1536
1537
                $assignments = $productStreamService->getStreamAssignments($streamId);
1538
                $sourceIds = $this->getHelper()->getArticleSourceIds($assignments->getArticleIds());
1539
                $items = $connectExport->fetchConnectItems($sourceIds, false);
1540
1541 View Code Duplication
                foreach ($items as $item) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1542
                    if ($productStreamService->allowToRemove($assignments, $streamId, $item['articleId'])) {
1543
                        $this->getSDK()->recordDelete($item['sourceId']);
1544
                        $removedRecords[] = $item['sourceId'];
1545
                    } else {
1546
                        //updates items with the new streams
1547
                        $streamCollection = $assignments->getStreamsByArticleId($item['articleId']);
1548
                        if (!$this->getHelper()->isMainVariant($item['sourceId']) || !$streamCollection) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $streamCollection of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1549
                            continue;
1550
                        }
1551
1552
                        //removes current stream from the collection
1553
                        unset($streamCollection[$streamId]);
1554
1555
                        $this->getSDK()->recordStreamAssignment(
1556
                            $item['sourceId'],
1557
                            $streamCollection,
1558
                            $item['groupId']
1559
                        );
1560
                    }
1561
                }
1562
1563
                $connectExport->updateConnectItemsStatus($removedRecords, Attribute::STATUS_DELETE);
1564
                $this->getSDK()->recordStreamDelete($streamId);
1565
                $productStreamService->changeStatus($streamId, ProductStreamService::STATUS_DELETE);
1566
            } catch (\Exception $e) {
1567
                $productStreamService->changeStatus($streamId, ProductStreamService::STATUS_ERROR);
1568
                $this->View()->assign([
1569
                    'success' => false,
1570
                    'message' => $e->getMessage()
1571
                ]);
1572
1573
                return;
1574
            }
1575
        }
1576
    }
1577
1578 View Code Duplication
    private function getMarketplaceApplier()
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...
1579
    {
1580
        if (!$this->marketplaceSettingsApplier) {
1581
            $this->marketplaceSettingsApplier = new MarketplaceSettingsApplier(
1582
                $this->getConfigComponent(),
1583
                Shopware()->Models(),
1584
                Shopware()->Db()
1585
            );
1586
        }
1587
1588
        return $this->marketplaceSettingsApplier;
1589
    }
1590
1591
    /**
1592
     * @return SnHttpClient
1593
     */
1594 View Code Duplication
    protected function getSnHttpClient()
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...
1595
    {
1596
        if (!$this->snHttpClient) {
1597
            $this->snHttpClient = new SnHttpClient(
1598
                $this->get('http_client'),
1599
                new \Shopware\Connect\Gateway\PDO(Shopware()->Db()->getConnection()),
1600
                $this->getConfigComponent()
1601
            );
1602
        }
1603
1604
        return $this->snHttpClient;
1605
    }
1606
1607
    /**
1608
     * @return string
1609
     */
1610
    protected function getHost()
1611
    {
1612
        $host = $this->getConfigComponent()->getConfig('connectDebugHost');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $host is correct as $this->getConfigComponen...fig('connectDebugHost') (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...
1613
        if (!$host || $host == '') {
1614
            $host = $this->getConfigComponent()->getMarketplaceUrl();
1615
        }
1616
1617
        return $host;
1618
    }
1619
1620
    /**
1621
     * @return ProductStreamService
1622
     */
1623
    protected function getProductStreamService()
1624
    {
1625
        if ($this->productStreamService === null) {
1626
            $this->productStreamService = $this->get('swagconnect.product_stream_service');
1627
        }
1628
1629
        return $this->productStreamService;
1630
    }
1631
1632
    /**
1633
     * {@inheritdoc}
1634
     */
1635
    public function getWhitelistedCSRFActions()
1636
    {
1637
        return ['login', 'autoLogin'];
1638
    }
1639
}
1640