GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Test Setup Failed
Push — filters ( 233a3a...88ffb1 )
by Alexander
29:32
created

ProductController   F

Complexity

Total Complexity 58

Size/Duplication

Total Lines 463
Duplicated Lines 15.98 %

Coupling/Cohesion

Components 1
Dependencies 29

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 58
lcom 1
cbo 29
dl 74
loc 463
rs 1.3043
c 2
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
F actionList() 20 195 32
C actionShow() 20 91 8
B actionSearch() 23 79 4
C buildBreadcrumbsArray() 11 62 14

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 ProductController 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 ProductController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace app\modules\shop\controllers;
4
5
use app\components\Controller;
6
use app\extensions\DefaultTheme\components\BaseWidget;
7
use app\extensions\DefaultTheme\models\ThemeActiveWidgets;
8
use app\extensions\DefaultTheme\models\ThemeWidgets;
9
use app\extensions\DefaultTheme\widgets\FilterSets\Widget;
10
use app\models\Object;
11
use app\models\PropertyStaticValues;
12
use app\models\Search;
13
use app\modules\core\helpers\ContentBlockHelper;
14
use app\modules\core\helpers\EventTriggeringHelper;
15
use app\modules\core\models\ContentBlock;
16
use app\modules\shop\events\ProductPageShowed;
17
use app\modules\shop\exceptions\EmptyFilterHttpException;
18
use app\modules\shop\models\Category;
19
use app\modules\shop\models\Product;
20
use app\traits\DynamicContentTrait;
21
use devgroup\TagDependencyHelper\ActiveRecordHelper;
22
use Yii;
23
use yii\caching\TagDependency;
24
use yii\data\Pagination;
25
use yii\helpers\ArrayHelper;
26
use yii\helpers\Json;
27
use yii\helpers\Url;
28
use yii\web\ForbiddenHttpException;
29
use yii\web\NotFoundHttpException;
30
use yii\web\Response;
31
use yii\web\ServerErrorHttpException;
32
33
class ProductController extends Controller
34
{
35
    use DynamicContentTrait;
36
37
    /**
38
     * Products listing by category with filtration support.
39
     *
40
     * @return string
41
     * @throws \Exception
42
     * @throws NotFoundHttpException
43
     * @throws ServerErrorHttpException
44
     */
45
    public function actionList()
0 ignored issues
show
Coding Style introduced by
actionList uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
actionList uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
46
    {
47
48
        $request = Yii::$app->request;
49
50
        if (null === $request->get('category_group_id')) {
51
            throw new NotFoundHttpException;
52
        }
53
54
        if (null === $object = Object::getForClass(Product::className())) {
55
            throw new ServerErrorHttpException('Object not found.');
56
        }
57
58
        $category_group_id = intval($request->get('category_group_id', 0));
59
60
        $title_append = $request->get('title_append', '');
61
        if (!empty($title_append)) {
62
            $title_append = is_array($title_append) ? implode(' ', $title_append) : $title_append;
63
            unset($_GET['title_append']);
64
        }
65
66
        $title_prepend = $request->get("title_prepend", "");
67
        if (!empty($title_prepend)) {
68
            $title_prepend = is_array($title_prepend) ? implode(" ", $title_prepend) : $title_prepend;
69
            unset($_GET["title_prepend"]);
70
        }
71
72
        $values_by_property_id = $request->get('properties', []);
73
        if (!is_array($values_by_property_id)) {
74
            $values_by_property_id = [$values_by_property_id];
75
        }
76
77
        if (Yii::$app->request->isPost && isset($_POST['properties'])) {
78
            if (is_array($_POST['properties'])) {
79
                foreach ($_POST['properties'] as $key => $value) {
80
                    if (isset($values_by_property_id[$key])) {
81
                        $values_by_property_id[$key] = array_unique(
82
                            ArrayHelper::merge(
83
                                $values_by_property_id[$key],
84
                                $value
85
                            )
86
                        );
87
                    } else {
88
                        $values_by_property_id[$key] = array_unique($value);
89
                    }
90
                }
91
            }
92
        }
93
94
        $selected_category_ids = $request->get('categories', []);
95
        if (!is_array($selected_category_ids)) {
96
            $selected_category_ids = [$selected_category_ids];
97
        }
98
99
        if (null !== $selected_category_id = $request->get('last_category_id')) {
100
            $selected_category_id = intval($selected_category_id);
101
        }
102
103
        $result = Product::filteredProducts(
104
            $category_group_id,
105
            $values_by_property_id,
106
            $selected_category_id,
107
            false,
108
            null,
109
            true,
110
            false
111
        );
112
        /** @var Pagination $pages */
113
        $pages = $result['pages'];
114
        if (Yii::$app->response->is_prefiltered_page) {
115
            $pages->route = '/' . Yii::$app->request->pathInfo;
116
            $pages->params = [
117
118
            ];
119
        }
120
        $allSorts = $result['allSorts'];
121
        $products = $result['products'];
122
123
        // throw 404 if we are at filtered page without any products
124
        if (!Yii::$app->request->isAjax && !empty($values_by_property_id) && empty($products)) {
125
            throw new EmptyFilterHttpException();
126
        }
127
128
        if (null !== $selected_category = $selected_category_id) {
129
            if ($selected_category_id > 0) {
130
                if (null !== $selected_category = Category::findById($selected_category_id, null)) {
131 View Code Duplication
                    if (!empty($selected_category->meta_description)) {
132
                        $this->view->registerMetaTag(
0 ignored issues
show
Bug introduced by
The method registerMetaTag does only exist in yii\web\View, but not in yii\base\View.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
133
                            [
134
                                'name' => 'description',
135
                                'content' => ContentBlockHelper::compileContentString(
136
                                    $selected_category->meta_description,
137
                                    Product::className() . ":{$selected_category->id}:meta_description",
138
                                    new TagDependency(
139
                                        [
140
                                            'tags' => [
141
                                                ActiveRecordHelper::getCommonTag(ContentBlock::className()),
142
                                                ActiveRecordHelper::getCommonTag(Category::className())
143
                                            ]
144
                                        ]
145
                                    )
146
                                )
147
                            ],
148
                            'meta_description'
149
                        );
150
                    }
151
152
                    $this->view->title = $selected_category->title;
153
                }
154
            }
155
        }
156
        if (is_null($selected_category) || !$selected_category->active) {
157
            throw new NotFoundHttpException;
158
        }
159
160
        if (!empty($title_append)) {
161
            $this->view->title .= " " . $title_append;
162
        }
163
164
        if (!empty($title_prepend)) {
165
            $this->view->title = "{$title_prepend} {$this->view->title}";
166
        }
167
168
        $this->view->blocks['h1'] = $selected_category->h1;
169
        $this->view->blocks['announce'] = $selected_category->announce;
170
        $this->view->blocks['content'] = $selected_category->content;
171
172
        $this->loadDynamicContent($object->id, 'shop/product/list', $request->get());
173
174
        $params = [
175
            'model' => $selected_category,
176
            'selected_category' => $selected_category,
177
            'selected_category_id' => $selected_category_id,
178
            'selected_category_ids' => $selected_category_ids,
179
            'values_by_property_id' => $values_by_property_id,
180
            'products' => $products,
181
            'object' => $object,
182
            'category_group_id' => $category_group_id,
183
            'pages' => $pages,
184
            'title_append' => $title_append,
185
            'selections' => $request->get(),
186
            'breadcrumbs' => $this->buildBreadcrumbsArray($selected_category, null, $values_by_property_id),
187
            'allSorts' => $allSorts,
188
        ];
189
        $viewFile = $this->computeViewFile($selected_category, 'list');
190
191
        if (Yii::$app->request->isAjax) {
192
            Yii::$app->response->format = Response::FORMAT_JSON;
193
194
            $content = $this->renderAjax(
195
                $viewFile,
196
                $params
197
            );
198
            $filters = '';
199
            $activeWidgets = ThemeActiveWidgets::getActiveWidgets();
200
            foreach ($activeWidgets as $activeWidget) {
201
                if ($activeWidget->widget->widget == Widget::className()) {
202
                    /** @var ThemeWidgets $widgetModel */
203
                    $widgetModel = $activeWidget->widget;
204
                    /** @var BaseWidget $widgetClassName */
205
                    $widgetClassName =  $widgetModel->widget;
206
                    $widgetConfiguration = Json::decode($widgetModel->configuration_json, true);
207
                    if (!is_array($widgetConfiguration)) {
208
                        $widgetConfiguration = [];
209
                    }
210
                    $activeWidgetConfiguration = Json::decode($activeWidget->configuration_json, true);
211
                    if (!is_array($activeWidgetConfiguration)) {
212
                        $activeWidgetConfiguration  = [];
213
                    }
214
                    $config = ArrayHelper::merge($widgetConfiguration, $activeWidgetConfiguration);
215
                    $config['themeWidgetModel'] = $widgetModel;
216
                    $config['partRow'] = $activeWidget->part;
217
                    $config['activeWidget'] = $activeWidget;
218
                    $filters = $widgetClassName::widget($config);
219
                }
220
            }
221
            return [
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('content' =...lues_by_property_id))); (array) is incompatible with the return type documented by app\modules\shop\control...tController::actionList of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
222
                'content' => $content,
223
                'filters' => $filters,
224
                'title' => $this->view->title,
225
                'url' => Url::to(
226
                    [
227
                        '/shop/product/list',
228
                        'last_category_id' => $selected_category_id,
229
                        'properties' => $values_by_property_id
230
                    ]
231
                ),
232
            ];
233
        } else {
234
            return $this->render(
235
                $viewFile,
236
                $params
237
            );
238
        }
239
    }
240
241
    /**
242
     * Product page view
243
     *
244
     * @param null $model_id
245
     * @return string
246
     * @throws NotFoundHttpException
247
     * @throws ServerErrorHttpException
248
     */
249
    public function actionShow($model_id = null)
250
    {
251
        if (null === $object = Object::getForClass(Product::className())) {
252
            throw new ServerErrorHttpException('Object not found.');
253
        }
254
255
        $cacheKey = 'Product:' . $model_id;
256
        if (false === $product = Yii::$app->cache->get($cacheKey)) {
257
            if (null === $product = Product::findById($model_id)) {
258
                throw new NotFoundHttpException;
259
            }
260
            Yii::$app->cache->set(
261
                $cacheKey,
262
                $product,
263
                86400,
264
                new TagDependency(
265
                    [
266
                        'tags' => [
267
                            ActiveRecordHelper::getObjectTag(Product::className(), $model_id),
268
                        ]
269
                    ]
270
                )
271
            );
272
        }
273
274
        $request = Yii::$app->request;
275
276
        $values_by_property_id = $request->get('properties', []);
277
        if (!is_array($values_by_property_id)) {
278
            $values_by_property_id = [$values_by_property_id];
279
        }
280
281
        $selected_category_id = $request->get('last_category_id');
282
283
        $selected_category_ids = $request->get('categories', []);
284
        if (!is_array($selected_category_ids)) {
285
            $selected_category_ids = [$selected_category_ids];
286
        }
287
288
        $category_group_id = intval($request->get('category_group_id', 0));
289
290
        // trigger that we are to show product to user!
291
        // wow! such product! very events!
292
        $specialEvent = new ProductPageShowed([
293
            'product_id' => $product->id,
294
        ]);
295
        EventTriggeringHelper::triggerSpecialEvent($specialEvent);
296
297 View Code Duplication
        if (!empty($product->meta_description)) {
298
            $this->view->registerMetaTag(
0 ignored issues
show
Bug introduced by
The method registerMetaTag does only exist in yii\web\View, but not in yii\base\View.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
299
                [
300
                    'name' => 'description',
301
                    'content' => ContentBlockHelper::compileContentString(
302
                        $product->meta_description,
303
                        Product::className() . ":{$product->id}:meta_description",
304
                        new TagDependency(
305
                            [
306
                                'tags' => [
307
                                    ActiveRecordHelper::getCommonTag(ContentBlock::className()),
308
                                    ActiveRecordHelper::getCommonTag(Product::className())
309
                                ]
310
                            ]
311
                        )
312
                    )
313
                ],
314
                'meta_description'
315
            );
316
        }
317
318
        $selected_category = ($selected_category_id > 0) ? Category::findById($selected_category_id) : null;
319
320
        $this->view->title = $product->title;
321
        $this->view->blocks['announce'] = $product->announce;
322
        $this->view->blocks['content'] = $product->content;
323
        $this->view->blocks['title'] = $product->title;
324
325
326
        return $this->render(
327
            $this->computeViewFile($product, 'show'),
328
            [
329
                'model' => $product,
330
                'category_group_id' => $category_group_id,
331
                'values_by_property_id' => $values_by_property_id,
332
                'selected_category_id' => $selected_category_id,
333
                'selected_category' => $selected_category,
334
                'selected_category_ids' => $selected_category_ids,
335
                'object' => $object,
336
                'breadcrumbs' => $this->buildBreadcrumbsArray($selected_category, $product)
0 ignored issues
show
Bug introduced by
It seems like $selected_category defined by $selected_category_id > ...ted_category_id) : null on line 318 can be null; however, app\modules\shop\control...buildBreadcrumbsArray() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
337
            ]
338
        );
339
    }
340
341
    /**
342
     * Search handler
343
     * @return array
344
     * @throws ForbiddenHttpException
345
     */
346
    public function actionSearch()
347
    {
348
        $headers = Yii::$app->response->getHeaders();
349
        $headers->set('X-Robots-Tag', 'none');
350
        $headers->set('X-Frame-Options', 'SAMEORIGIN');
351
        $headers->set('X-Content-Type-Options', 'nosniff');
352
        if (!Yii::$app->request->isAjax) {
353
            throw new ForbiddenHttpException();
354
        }
355
        $model = new Search();
356
        $model->load(Yii::$app->request->get());
357
        $cacheKey = 'ProductSearchIds: ' . $model->q;
358
        $ids = Yii::$app->cache->get($cacheKey);
359
        if ($ids === false) {
360
            $ids = ArrayHelper::merge(
361
                $model->searchProductsByDescription(),
362
                $model->searchProductsByProperty()
363
            );
364
            Yii::$app->cache->set(
365
                $cacheKey,
366
                $ids,
367
                86400,
368
                new TagDependency(
369
                    [
370
                        'tags' => ActiveRecordHelper::getCommonTag(Product::className()),
371
                    ]
372
                )
373
            );
374
        }
375
376
        /** @var \app\modules\shop\ShopModule $module */
377
        $module = Yii::$app->modules['shop'];
378
379
        $pages = new Pagination(
380
            [
381
                'defaultPageSize' => $module->searchResultsLimit,
382
                'forcePageParam' => false,
383
                'totalCount' => count($ids),
384
            ]
385
        );
386
        $cacheKey .= ' : ' . $pages->offset;
387
        $products = Yii::$app->cache->get($cacheKey);
388 View Code Duplication
        if ($products === false) {
389
            $products = Product::find()->where(
390
                [
391
                    'in',
392
                    '`id`',
393
                    array_slice(
394
                        $ids,
395
                        $pages->offset,
396
                        $pages->limit
397
                    )
398
                ]
399
            )->addOrderBy('sort_order')->with('images')->all();
400
            Yii::$app->cache->set(
401
                $cacheKey,
402
                $products,
403
                86400,
404
                new TagDependency(
405
                    [
406
                        'tags' => ActiveRecordHelper::getCommonTag(Product::className()),
407
                    ]
408
                )
409
            );
410
        }
411
        Yii::$app->response->format = Response::FORMAT_JSON;
412
        return [
413
            'view' => $this->renderAjax(
414
                'search',
415
                [
416
                    'model' => $model,
417
                    'pages' => $pages,
418
                    'products' => $products,
419
                ]
420
            ),
421
            'totalCount' => count($ids),
422
        ];
423
424
    }
425
426
    /**
427
    * This function build array for widget "Breadcrumbs"
428
    * @param Category $selCat - model of current category
429
    * @param Product|null $product - model of product, if current page is a page of product
430
    * @param array $properties - array of properties and static values
431
    * Return an array for widget or empty array
432
    */
433
    private function buildBreadcrumbsArray($selCat, $product = null, $properties = [])
434
    {
435
        if ($selCat === null) {
436
            return [];
437
        }
438
439
        // init
440
        $breadcrumbs = [];
441
        if ($product !== null) {
442
            $crumbs[$product->slug] = !empty($product->breadcrumbs_label) ? $product->breadcrumbs_label : '';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$crumbs was never initialized. Although not strictly required by PHP, it is generally a good practice to add $crumbs = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
443
        }
444
        $crumbs[$selCat->slug] = $selCat->breadcrumbs_label;
0 ignored issues
show
Bug introduced by
The variable $crumbs does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
445
446
        // get basic data
447
        $parent = $selCat->parent_id > 0 ? Category::findById($selCat->parent_id) : null;
448 View Code Duplication
        while ($parent !== null) {
449
            $crumbs[$parent->slug] = $parent->breadcrumbs_label;
450
            $parent = $parent->parent;
451
        }
452
453
        // build array for widget
454
        $url = '';
455
        $crumbs = array_reverse($crumbs, true);
456 View Code Duplication
        foreach ($crumbs as $slug => $label) {
457
            $url .= '/' . $slug;
458
            $breadcrumbs[] = [
459
                'label' => $label,
460
                'url' => $url
461
            ];
462
        }
463
        if (is_null($product) && $this->module->showFiltersInBreadcrumbs && !empty($properties)) {
464
            $route = [
465
                '@category',
466
                'last_category_id' => $selCat->id,
467
                'category_group_id' => $selCat->category_group_id,
468
            ];
469
            $params = [];
470
            foreach ($properties as $propertyId => $propertyStaticValues) {
471
                $localParams = $params;
472
                foreach ($propertyStaticValues as $propertyStaticValue) {
473
                    $psv = PropertyStaticValues::findById($propertyStaticValue);
474
                    if (is_null($psv)) {
475
                        continue;
476
                    }
477
                    $localParams[$propertyId][] = $propertyStaticValue;
478
                    $breadcrumbs[] = [
479
                        'label' => $psv['name'],
480
                        'url' => array_merge($route, ['properties' => $localParams]),
481
                    ];
482
                }
483
                $params[$propertyId] = $propertyStaticValues;
484
            }
485
        }
486
        unset($breadcrumbs[count($breadcrumbs) - 1]['url']); // last item is not a link
487
488
        if (isset(Yii::$app->response->blocks['breadcrumbs_label'])) {
489
            // last item label rewrited through prefiltered page or something similar
490
            $breadcrumbs[count($breadcrumbs) - 1]['label'] = Yii::$app->response->blocks['breadcrumbs_label'];
491
        }
492
493
        return $breadcrumbs;
494
    }
495
}
496