CategoryExtractor   F
last analyzed

Complexity

Total Complexity 69

Size/Duplication

Total Lines 606
Duplicated Lines 2.31 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 14
loc 606
rs 2.874
c 0
b 0
f 0
wmc 69
lcom 1
cbo 3

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A extractImportedCategories() 0 10 2
A getCategoryIdsCollection() 0 4 1
A collectCategoryIds() 0 13 3
B getRemoteCategoriesTree() 0 52 7
A getRemoteCategoriesTreeByStream() 0 17 2
A getMainNodes() 0 23 3
A hasShopItems() 0 19 2
A extractByShopId() 0 9 1
A getStreamsByShopId() 7 23 2
A getNodesByQuery() 0 23 4
A extractNode() 0 12 2
B getQueryStreams() 7 43 5
B getMainCategoriesByQuery() 0 47 7
A getChildrenCategoriesByQuery() 0 12 1
B getUniqueParents() 0 27 6
A getCategoryNames() 0 26 4
C convertTree() 0 49 12
A getQueryCategories() 0 27 3
A isLeaf() 0 10 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CategoryExtractor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CategoryExtractor, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * (c) shopware AG <[email protected]>
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace ShopwarePlugins\Connect\Components;
9
10
use Shopware\Connect\Gateway;
11
use Shopware\Models\Category\Category;
12
use Shopware\CustomModels\Connect\AttributeRepository;
13
use Enlight_Components_Db_Adapter_Pdo_Mysql as Pdo;
14
15
/**
16
 * Class CategoryExtractor
17
 * @package Shopware\CustomModels\Connect
18
 */
19
class CategoryExtractor
20
{
21
    /**
22
     * @var \Shopware\CustomModels\Connect\AttributeRepository
23
     */
24
    private $attributeRepository;
25
26
    /**
27
     * @var \ShopwarePlugins\Connect\Components\CategoryResolver
28
     */
29
    private $categoryResolver;
30
31
    /**
32
     * @var \Shopware\Connect\Gateway
33
     */
34
    private $configurationGateway;
35
36
    /**
37
     * @var \ShopwarePlugins\Connect\Components\RandomStringGenerator;
38
     */
39
    private $randomStringGenerator;
40
41
    /**
42
     * @var \Enlight_Components_Db_Adapter_Pdo_Mysql
43
     */
44
    private $db;
45
46
    /**
47
     * @param AttributeRepository $attributeRepository
48
     * @param CategoryResolver $categoryResolver
49
     * @param Gateway $configurationGateway
50
     * @param RandomStringGenerator $randomStringGenerator
51
     */
52
    public function __construct(
53
        AttributeRepository $attributeRepository,
54
        CategoryResolver $categoryResolver,
55
        Gateway $configurationGateway,
56
        RandomStringGenerator $randomStringGenerator,
57
        Pdo $db
58
    ) {
59
        $this->attributeRepository = $attributeRepository;
60
        $this->categoryResolver = $categoryResolver;
61
        $this->configurationGateway = $configurationGateway;
62
        $this->randomStringGenerator = $randomStringGenerator;
63
        $this->db = $db;
64
    }
65
66
    /**
67
     * Collects categories
68
     * from imported Shopware Connect products
69
     */
70
    public function extractImportedCategories()
71
    {
72
        $categories = [];
73
        /** @var \Shopware\CustomModels\Connect\Attribute $attribute */
74
        foreach ($this->attributeRepository->findRemoteArticleAttributes() as $attribute) {
75
            $categories = array_merge($categories, $attribute->getCategory());
76
        }
77
78
        return $this->convertTree($this->categoryResolver->generateTree($categories));
79
    }
80
81
    /**
82
     * @param Category $category
83
     * @return array
84
     */
85
    public function getCategoryIdsCollection(Category $category)
86
    {
87
        return $this->collectCategoryIds($category);
88
    }
89
90
    /**
91
     * Collects connect category ids
92
     *
93
     * @param Category $parentCategory
94
     * @param array|null $categoryIds
95
     * @return array
96
     */
97
    private function collectCategoryIds(Category $parentCategory, array $categoryIds = [])
98
    {
99
        //is connect category
100
        if ($parentCategory->getAttribute()->getConnectImportedCategory()) {
101
            $categoryIds[] = $parentCategory->getId();
102
        }
103
104
        foreach ($parentCategory->getChildren() as $category) {
105
            $categoryIds = $this->collectCategoryIds($category, $categoryIds);
106
        }
107
108
        return $categoryIds;
109
    }
110
111
    /**
112
     * Loads remote categories
113
     *
114
     * @param string|null $parent
115
     * @param bool|null $includeChildren
116
     * @param bool|null $excludeMapped
117
     * @param int|null $shopId
118
     * @param string|null $stream
119
     * @return array
120
     */
121
    public function getRemoteCategoriesTree($parent = null, $includeChildren = false, $excludeMapped = false, $shopId = null, $stream = null)
122
    {
123
        $sql = '
124
            SELECT pcc.category_key, pcc.label
125
            FROM s_plugin_connect_items pci
126
            INNER JOIN `s_plugin_connect_product_to_categories` pcptc ON pci.article_id = pcptc.articleID
127
            INNER JOIN `s_plugin_connect_categories` pcc ON pcptc.connect_category_id = pcc.id
128
        ';
129
130
        $whereParams = [];
131
        $whereSql = [];
132
133
        if ($shopId > 0) {
134
            $whereSql[] = 'pci.shop_id = ?';
135
            $whereParams[] = (string) $shopId;
136
        }
137
138
        if ($stream) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stream of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
139
            $whereSql[] = 'pci.stream = ?';
140
            $whereParams[] = $stream;
141
        }
142
143
        if ($parent !== null) {
144
            $whereSql[] = 'pcc.category_key LIKE ?';
145
            $whereParams[] = $parent . '/%';
146
        }
147
148
        if ($excludeMapped === true) {
149
            $sql .= ' INNER JOIN `s_articles_attributes` ar ON ar.articleDetailsID = pci.article_detail_id';
150
            $whereSql[] = '(ar.connect_mapped_category <> 1 OR ar.connect_mapped_category IS NULL)';
151
        }
152
153
        if (count($whereSql) > 0) {
154
            $sql .= sprintf(' WHERE %s', implode(' AND ', $whereSql));
155
        }
156
157
        $rows = $this->db->fetchPairs($sql, $whereParams);
158
159
        $parent = $parent ?: '';
160
        // if parent is an empty string, filter only main categories, otherwise
161
        // filter only first child categories
162
        $rows = $this->convertTree(
163
            $this->categoryResolver->generateTree($rows, $parent),
164
            $includeChildren,
0 ignored issues
show
Bug introduced by
It seems like $includeChildren defined by parameter $includeChildren on line 121 can also be of type null; however, ShopwarePlugins\Connect\...xtractor::convertTree() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
165
            false,
166
            false,
167
            $shopId,
168
            $stream
169
        );
170
171
        return $rows;
172
    }
173
174
    /**
175
     * Collects remote categories by given stream and shopId
176
     *
177
     * @param string $stream
178
     * @param int $shopId
179
     * @param bool $hideMapped
180
     * @return array
181
     */
182
    public function getRemoteCategoriesTreeByStream($stream, $shopId, $hideMapped = false)
183
    {
184
        $sql = 'SELECT cat.category_key, cat.label
185
                FROM s_plugin_connect_items attributes
186
                INNER JOIN `s_plugin_connect_product_to_categories` prod_to_cat ON attributes.article_id = prod_to_cat.articleID
187
                INNER JOIN `s_plugin_connect_categories` cat ON prod_to_cat.connect_category_id = cat.id';
188
        $whereClause = ' WHERE attributes.shop_id = ? AND attributes.stream = ?';
189
        if ($hideMapped) {
190
            $sql .= ' INNER JOIN `s_articles_attributes` ar ON ar.articleDetailsID = attributes.article_detail_id';
191
            $whereClause .= ' AND ar.connect_mapped_category IS NULL';
192
        }
193
194
        $sql .= $whereClause;
195
        $rows = $this->db->fetchPairs($sql, [(int) $shopId, $stream]);
196
197
        return $this->convertTree($this->categoryResolver->generateTree($rows), false, false, false, $shopId, $stream);
198
    }
199
200
    /**
201
     * Collects supplier names as categories tree
202
     * @param null $excludeMapped
203
     * @param bool $expanded
204
     * @return array
205
     */
206
    public function getMainNodes($excludeMapped = null, $expanded = false)
207
    {
208
        // if parent is null collect shop names
209
        $shops = [];
210
        foreach ($this->configurationGateway->getConnectedShopIds() as $shopId) {
211
            if (!$this->hasShopItems($shopId, $excludeMapped)) {
0 ignored issues
show
Documentation introduced by
$excludeMapped is of type null, but the function expects a boolean.

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...
212
                continue;
213
            }
214
            $configuration = $this->configurationGateway->getShopConfiguration($shopId);
215
            $shops[$shopId] = [
216
                'name' => $configuration->displayName,
217
                'iconCls' => 'sc-tree-node-icon',
218
                'icon' => $configuration->logoUrl,
219
            ];
220
        }
221
222
        $tree = $this->convertTree($shops, false, $expanded);
223
        array_walk($tree, function (&$node) {
224
            $node['leaf'] = false;
225
        });
226
227
        return $tree;
228
    }
229
230
    /**
231
     * @param $shopId
232
     * @param bool $excludeMapped
233
     * @return bool
234
     */
235
    public function hasShopItems($shopId, $excludeMapped = false)
236
    {
237
        $sql = 'SELECT COUNT(pci.id)
238
                FROM `s_plugin_connect_items` pci
239
        ';
240
241
        $whereClause = ' WHERE pci.shop_id = ?';
242
243
        if ($excludeMapped === true) {
244
            $sql .= ' INNER JOIN `s_articles_attributes` aa ON aa.articleDetailsID = pci.article_detail_id';
245
            $whereClause .= ' AND aa.connect_mapped_category IS NULL';
246
        }
247
248
        $sql .= $whereClause;
249
250
        $count = $this->db->fetchOne($sql, [(string) $shopId]);
251
252
        return $count > 0;
253
    }
254
255
    /**
256
     * Collects categories from products
257
     * by given shopId
258
     *
259
     * @param int $shopId
260
     * @param bool $includeChildren
261
     * @return array
262
     */
263
    public function extractByShopId($shopId, $includeChildren = false)
264
    {
265
        $sql = 'SELECT category_key, label
266
                FROM `s_plugin_connect_categories` cat
267
                WHERE cat.shop_id = ?';
268
        $rows = $this->db->fetchPairs($sql, [$shopId]);
269
270
        return $this->convertTree($this->categoryResolver->generateTree($rows), $includeChildren);
271
    }
272
273
    public function getStreamsByShopId($shopId)
274
    {
275
        $sql = 'SELECT DISTINCT(stream)
276
                FROM `s_plugin_connect_items` attributes
277
                WHERE attributes.shop_id = ?';
278
        $rows = $this->db->fetchCol($sql, [(string) $shopId]);
279
280
        $streams = [];
281 View Code Duplication
        foreach ($rows as $streamName) {
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...
282
            $id = sprintf('%s_stream_%s', $shopId, $streamName);
283
            $streams[$id] = [
284
                'name' => $streamName,
285
                'iconCls' => 'sprite-product-streams',
286
            ];
287
        }
288
289
        $tree = $this->convertTree($streams, false);
290
        array_walk($tree, function (&$node) {
291
            $node['leaf'] = false;
292
        });
293
294
        return $tree;
295
    }
296
297
    public function getNodesByQuery($hideMapped, $query, $parent, $node)
298
    {
299
        switch ($parent) {
300
            case 'root':
301
                $categories = $this->getMainNodes($hideMapped, true);
302
                break;
303
            case is_numeric($parent):
304
                $categories = $this->getQueryStreams($parent, $query, $hideMapped);
305
                break;
306
            case strpos($parent, '_stream_') > 0:
307
                list($shopId, $stream) = explode('_stream_', $parent);
308
                $categories = $this->getMainCategoriesByQuery($shopId, $stream, $query, $hideMapped);
309
                break;
310
            default:
311
                // given id must have following structure:
312
                // shopId5~stream~AwesomeProducts~/english/boots/nike
313
                // shopId is required parameter to fetch all child categories of this parent
314
                list($shopId, $stream) = $this->extractNode($node);
315
                $categories = $this->getChildrenCategoriesByQuery($parent, $query, $hideMapped, $shopId, $stream);
316
        }
317
318
        return $categories;
319
    }
320
321
    /**
322
     * Returns shopId and stream from given node,
323
     * it must have specific format.
324
     *
325
     * shopId5~stream~AwesomeProducts~/english/boots/nike
326
     *
327
     * Used in remote category tree to identify which category
328
     * to which shopId and stream belongs.
329
     *
330
     * Returned array has following structure
331
     * [ shopId, stream]
332
     *
333
     * @param string $node
334
     * @throws \InvalidArgumentException
335
     * @return array
336
     */
337
    public function extractNode($node)
338
    {
339
        preg_match('/^(shopId(\d+)~)(stream~(.*)~)(.*)$/', $node, $matches);
340
        if (empty($matches)) {
341
            throw new \InvalidArgumentException('Node must contain shopId and stream');
342
        }
343
344
        return [
345
            $matches[2],
346
            $matches[4]
347
        ];
348
    }
349
350
    /**
351
     * @param $shopId
352
     * @param $query
353
     * @param $hideMapped
354
     * @param int $shopId
355
     * @return array
356
     */
357
    public function getQueryStreams($shopId, $query, $hideMapped)
358
    {
359
        $rows = $this->getQueryCategories($query, $shopId, null, $hideMapped);
360
361
        if (count($rows) === 0) {
362
            return [];
363
        }
364
365
        $sql = 'SELECT DISTINCT(attributes.stream)
366
                FROM `s_plugin_connect_categories` cat
367
                INNER JOIN `s_plugin_connect_product_to_categories` prod_to_cat ON cat.id = prod_to_cat.connect_category_id
368
                INNER JOIN `s_plugin_connect_items` attributes ON prod_to_cat.articleID = attributes.article_id
369
                WHERE attributes.shop_id = ?  AND (';
370
371
        $params = [$shopId];
372
        foreach ($rows as $categoryKey => $label) {
373
            if ($categoryKey !== reset(array_keys($rows))) {
0 ignored issues
show
Bug introduced by
array_keys($rows) cannot be passed to reset() as the parameter $array expects a reference.
Loading history...
374
                $sql .= ' OR ';
375
            }
376
377
            $sql .= ' cat.category_key = ?';
378
            $params[] = $categoryKey;
379
        }
380
381
        $sql .= ' )';
382
        $rows = $this->db->fetchCol($sql, $params);
383
        $streams = [];
384
385 View Code Duplication
        foreach ($rows as $streamName) {
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...
386
            $id = sprintf('%s_stream_%s', $shopId, $streamName);
387
            $streams[$id] = [
388
                'name' => $streamName,
389
                'iconCls' => 'sprite-product-streams',
390
            ];
391
        }
392
393
        $tree = $this->convertTree($streams, false, true);
394
        array_walk($tree, function (&$node) {
395
            $node['leaf'] = false;
396
        });
397
398
        return $tree;
399
    }
400
401
    /**
402
     * @param $shopId
403
     * @param $stream
404
     * @param $query
405
     * @param $hideMapped
406
     * @return array
407
     */
408
    public function getMainCategoriesByQuery($shopId, $stream, $query, $hideMapped)
409
    {
410
        $rows = $this->getQueryCategories($query, $shopId, $stream, $hideMapped);
411
412
        $rootCategories = [];
413
414
        foreach ($rows as $key => $name) {
415
            $position = strpos($key, '/', 1);
416
417
            if ($position === false) {
418
                $rootCategory = $key;
419
            } else {
420
                $rootCategory = substr($key, 0, $position);
421
            }
422
423
            if (!in_array($rootCategory, $rootCategories)) {
424
                $rootCategories[] = $rootCategory;
425
            }
426
        }
427
428
        if (count($rootCategories) === 0) {
429
            return [];
430
        }
431
432
        $sql = 'SELECT DISTINCT(category_key), label
433
                FROM `s_plugin_connect_categories` cat
434
                INNER JOIN `s_plugin_connect_product_to_categories` prod_to_cat ON cat.id = prod_to_cat.connect_category_id
435
                INNER JOIN `s_plugin_connect_items` attributes ON prod_to_cat.articleID = attributes.article_id
436
                WHERE attributes.shop_id = ? AND attributes.stream = ?  AND (';
437
438
        $params = [$shopId, $stream];
439
440
        foreach ($rootCategories as $item) {
441
            if ($item !== $rootCategories[0]) {
442
                $sql .= ' OR ';
443
            }
444
445
            $sql .= ' cat.category_key LIKE ?';
446
            $params[] = $item . '%';
447
        }
448
449
        $sql .= ' )';
450
451
        $rows = $this->db->fetchPairs($sql, $params);
452
453
        return $this->convertTree($this->categoryResolver->generateTree($rows), false, true, false, $shopId, $stream);
454
    }
455
456
    public function getChildrenCategoriesByQuery($parent, $query, $hideMapped, $shopId, $stream)
457
    {
458
        $rows = $this->getQueryCategories($query, $shopId, $stream, $hideMapped, $parent);
459
460
        $parents = $this->getUniqueParents($rows, $parent);
461
462
        $categoryKeys = array_unique(array_merge(array_keys($rows), $parents));
463
464
        $result = $this->getCategoryNames($categoryKeys, $shopId);
465
466
        return $this->convertTree($this->categoryResolver->generateTree($result, $parent), false, true, true, $shopId, $stream);
467
    }
468
469
    public function getUniqueParents($rows, $parent)
470
    {
471
        $parents = [];
472
473
        foreach ($rows as $key => $name) {
474
            $position = strrpos($key, '/', 1);
475
476
            if ($position === false) {
477
                continue;
478
            }
479
480
            while ($position !== strlen($parent)) {
481
                $newParent = substr($key, 0, $position);
482
                $position = strrpos($newParent, '/', 1);
483
484
                if ($position === false) {
485
                    break;
486
                }
487
488
                if (!in_array($newParent, $parents)) {
489
                    $parents[] = $newParent;
490
                }
491
            }
492
        }
493
494
        return $parents;
495
    }
496
497
    /**
498
     * @param array $categoryKeys
499
     * @param int $shopId
500
     * @return array
501
     */
502
    public function getCategoryNames($categoryKeys, $shopId)
503
    {
504
        if (count($categoryKeys) === 0) {
505
            return [];
506
        }
507
508
        $params = [];
509
510
        $sql = 'SELECT category_key, label
511
                FROM `s_plugin_connect_categories` cat';
512
513
        foreach ($categoryKeys as $categoryKey) {
514
            if ($categoryKey === $categoryKeys[0]) {
515
                $sql .= ' WHERE cat.category_key = ?';
516
            } else {
517
                $sql .= ' OR cat.category_key = ?';
518
            }
519
            $params[] = $categoryKey;
520
        }
521
        $sql .= ' AND cat.shop_id = ?';
522
        $params[] = $shopId;
523
524
        $rows = $this->db->fetchPairs($sql, $params);
525
526
        return $rows;
527
    }
528
529
    /**
530
     * Converts categories tree structure
531
     * to be usable in ExtJS tree
532
     *
533
     * @param array $tree
534
     * @return array
535
     */
536
    private function convertTree(array $tree, $includeChildren = true, $expanded = false, $checkLeaf = false, $shopId = null, $stream = null)
537
    {
538
        $categories = [];
539
        foreach ($tree as $id => $node) {
540
            $children = [];
541
            if ($includeChildren === true && !empty($node['children'])) {
542
                $children = $this->convertTree($node['children'], $includeChildren);
543
            }
544
545
            if (strlen($node['name']) === 0) {
546
                continue;
547
            }
548
549
            $prefix = '';
550
            if ($shopId > 0) {
551
                $prefix .= sprintf('shopId%s~', $shopId);
552
            }
553
554
            if ($stream) {
555
                $prefix .= sprintf('stream~%s~', $stream);
556
            }
557
558
            $category = [
559
                'name' => $node['name'],
560
                'id' => $this->randomStringGenerator->generate($prefix . $id),
561
                'categoryId' => $id,
562
                'leaf' => empty($node['children']) ? true : false,
563
                'children' => $children,
564
                'cls' => 'sc-tree-node',
565
                'expanded' => $expanded
566
            ];
567
568
            if ($checkLeaf && $category['leaf'] == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
569
                $category['leaf'] = $this->isLeaf($id);
570
            }
571
572
            if (isset($node['iconCls'])) {
573
                $category['iconCls'] = $node['iconCls'];
574
            }
575
576
            if (isset($node['icon'])) {
577
                $category['icon'] = $node['icon'];
578
            }
579
580
            $categories[] = $category;
581
        }
582
583
        return $categories;
584
    }
585
586
    public function getQueryCategories($query, $shopId, $stream = null, $excludeMapped = false, $parent = '')
587
    {
588
        $sql = 'SELECT category_key, label
589
                FROM `s_plugin_connect_categories` cat
590
                INNER JOIN `s_plugin_connect_product_to_categories` prod_to_cat ON cat.id = prod_to_cat.connect_category_id
591
                INNER JOIN `s_plugin_connect_items` attributes ON prod_to_cat.articleID = attributes.article_id
592
                INNER JOIN `s_articles_attributes` ar ON ar.articleID = attributes.article_id
593
                WHERE cat.label LIKE ? AND cat.category_key LIKE ? AND attributes.shop_id = ?';
594
        $whereParams = [
595
            '%' . $query . '%',
596
            $parent . '%',
597
            $shopId,
598
        ];
599
600
        if ($excludeMapped === true) {
601
            $sql .= ' AND ar.connect_mapped_category IS NULL';
602
        }
603
604
        if ($stream) {
605
            $sql .= '  AND attributes.stream = ?';
606
            $whereParams[] = $stream;
607
        }
608
609
        $rows = $this->db->fetchPairs($sql, $whereParams);
610
611
        return $rows;
612
    }
613
614
    public function isLeaf($categoryId)
615
    {
616
        $sql = 'SELECT COUNT(id)
617
                FROM `s_plugin_connect_categories` cat
618
                WHERE cat.category_key LIKE ?';
619
620
        $count = $this->db->fetchOne($sql, [$categoryId . '/%']);
621
622
        return $count == 0;
623
    }
624
}
625