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.
Passed
Push — master ( 2d5011...ba6635 )
by
unknown
10:06
created

ProductController::behaviors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
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
use app\modules\seo\behaviors\SetCanonicalBehavior;
33
34
class ProductController extends Controller
35
{
36
    use DynamicContentTrait;
37
38
    public function behaviors()
39
    {
40
        return [
41
            [
42
                'class' => SetCanonicalBehavior::className()
43
            ]
44
        ];
45
    }
46
47
    /**
48
     * Products listing by category with filtration support.
49
     *
50
     * @return string
51
     * @throws \Exception
52
     * @throws NotFoundHttpException
53
     * @throws ServerErrorHttpException
54
     */
55
    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...
56
    {
57
58
        $request = Yii::$app->request;
59
60
        if (null === $request->get('category_group_id')) {
61
            throw new NotFoundHttpException;
62
        }
63
64
        if (null === $object = Object::getForClass(Product::className())) {
65
            throw new ServerErrorHttpException('Object not found.');
66
        }
67
68
        $category_group_id = intval($request->get('category_group_id', 0));
69
70
        $title_append = $request->get('title_append', '');
71
        if (!empty($title_append)) {
72
            $title_append = is_array($title_append) ? implode(' ', $title_append) : $title_append;
73
            unset($_GET['title_append']);
74
        }
75
76
        $title_prepend = $request->get("title_prepend", "");
77
        if (!empty($title_prepend)) {
78
            $title_prepend = is_array($title_prepend) ? implode(" ", $title_prepend) : $title_prepend;
79
            unset($_GET["title_prepend"]);
80
        }
81
82
        $values_by_property_id = $request->get('properties', []);
83
        if (!is_array($values_by_property_id)) {
84
            $values_by_property_id = [$values_by_property_id];
85
        }
86
87
        if (Yii::$app->request->isPost && isset($_POST['properties'])) {
88
            if (is_array($_POST['properties'])) {
89
                foreach ($_POST['properties'] as $key => $value) {
90
                    if (isset($values_by_property_id[$key])) {
91
                        $values_by_property_id[$key] = array_unique(
92
                            ArrayHelper::merge(
93
                                $values_by_property_id[$key],
94
                                $value
95
                            )
96
                        );
97
                    } else {
98
                        $values_by_property_id[$key] = array_unique($value);
99
                    }
100
                }
101
            }
102
        }
103
104
        $selected_category_ids = $request->get('categories', []);
105
        if (!is_array($selected_category_ids)) {
106
            $selected_category_ids = [$selected_category_ids];
107
        }
108
109
        if (null !== $selected_category_id = $request->get('last_category_id')) {
110
            $selected_category_id = intval($selected_category_id);
111
        }
112
113
        $result = Product::filteredProducts(
114
            $category_group_id,
115
            $values_by_property_id,
116
            $selected_category_id,
117
            false,
118
            null,
119
            true,
120
            false
121
        );
122
        /** @var Pagination $pages */
123
        $pages = $result['pages'];
124
        if (Yii::$app->response->is_prefiltered_page) {
125
            $pages->route = '/' . Yii::$app->request->pathInfo;
126
            $pages->params = [
127
128
            ];
129
        }
130
        $allSorts = $result['allSorts'];
131
        $products = $result['products'];
132
133
        // throw 404 if we are at filtered page without any products
134
        if (!Yii::$app->request->isAjax && !empty($values_by_property_id) && empty($products)) {
135
            throw new EmptyFilterHttpException();
136
        }
137
138
        if (null !== $selected_category = $selected_category_id) {
139
            if ($selected_category_id > 0) {
140
                if (null !== $selected_category = Category::findById($selected_category_id, null)) {
141 View Code Duplication
                    if (!empty($selected_category->meta_description)) {
142
                        $this->view->registerMetaTag(
143
                            [
144
                                'name' => 'description',
145
                                'content' => ContentBlockHelper::compileContentString(
146
                                    $selected_category->meta_description,
147
                                    Product::className() . ":{$selected_category->id}:meta_description",
148
                                    new TagDependency(
149
                                        [
150
                                            'tags' => [
151
                                                ActiveRecordHelper::getCommonTag(ContentBlock::className()),
152
                                                ActiveRecordHelper::getCommonTag(Category::className())
153
                                            ]
154
                                        ]
155
                                    )
156
                                )
157
                            ],
158
                            'meta_description'
159
                        );
160
                    }
161
162
                    $this->view->title = $selected_category->title;
163
                }
164
            }
165
        }
166
        if (is_null($selected_category) || !$selected_category->active) {
167
            throw new NotFoundHttpException;
168
        }
169
170
        if (!empty($title_append)) {
171
            $this->view->title .= " " . $title_append;
172
        }
173
174
        if (!empty($title_prepend)) {
175
            $this->view->title = "{$title_prepend} {$this->view->title}";
176
        }
177
178
        $this->view->blocks['h1'] = $selected_category->h1;
179
        $this->view->blocks['announce'] = $selected_category->announce;
180
        $this->view->blocks['content'] = $selected_category->content;
181
182
        $this->loadDynamicContent($object->id, 'shop/product/list', $request->get());
183
184
        $params = [
185
            'model' => $selected_category,
186
            'selected_category' => $selected_category,
187
            'selected_category_id' => $selected_category_id,
188
            'selected_category_ids' => $selected_category_ids,
189
            'values_by_property_id' => $values_by_property_id,
190
            'products' => $products,
191
            'object' => $object,
192
            'category_group_id' => $category_group_id,
193
            'pages' => $pages,
194
            'title_append' => $title_append,
195
            'selections' => $request->get(),
196
            'breadcrumbs' => $this->buildBreadcrumbsArray($selected_category, null, $values_by_property_id),
197
            'allSorts' => $allSorts,
198
        ];
199
        $viewFile = $this->computeViewFile($selected_category, 'list');
200
201
        if (Yii::$app->request->isAjax) {
202
            Yii::$app->response->format = Response::FORMAT_JSON;
203
204
            $content = $this->renderAjax(
205
                $viewFile,
206
                $params
207
            );
208
            $filters = '';
209
            $activeWidgets = ThemeActiveWidgets::getActiveWidgets();
210
            foreach ($activeWidgets as $activeWidget) {
211
                if ($activeWidget->widget->widget == Widget::className()) {
212
                    /** @var ThemeWidgets $widgetModel */
213
                    $widgetModel = $activeWidget->widget;
214
                    /** @var BaseWidget $widgetClassName */
215
                    $widgetClassName =  $widgetModel->widget;
216
                    $widgetConfiguration = Json::decode($widgetModel->configuration_json, true);
217
                    if (!is_array($widgetConfiguration)) {
218
                        $widgetConfiguration = [];
219
                    }
220
                    $activeWidgetConfiguration = Json::decode($activeWidget->configuration_json, true);
221
                    if (!is_array($activeWidgetConfiguration)) {
222
                        $activeWidgetConfiguration  = [];
223
                    }
224
                    $config = ArrayHelper::merge($widgetConfiguration, $activeWidgetConfiguration);
225
                    $config['themeWidgetModel'] = $widgetModel;
226
                    $config['partRow'] = $activeWidget->part;
227
                    $config['activeWidget'] = $activeWidget;
228
                    $filters = $widgetClassName::widget($config);
229
                }
230
            }
231
            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...
232
                'content' => $content,
233
                'filters' => $filters,
234
                'title' => $this->view->title,
235
                'url' => Url::to(
236
                    [
237
                        '/shop/product/list',
238
                        'last_category_id' => $selected_category_id,
239
                        'category_group_id' => $category_group_id,
240
                        'properties' => $values_by_property_id
241
                    ]
242
                ),
243
            ];
244
        } else {
245
            return $this->render(
246
                $viewFile,
247
                $params
248
            );
249
        }
250
    }
251
252
    /**
253
     * Product page view
254
     *
255
     * @param null $model_id
256
     * @return string
257
     * @throws NotFoundHttpException
258
     * @throws ServerErrorHttpException
259
     */
260
    public function actionShow($model_id = null)
261
    {
262
        if (null === $object = Object::getForClass(Product::className())) {
263
            throw new ServerErrorHttpException('Object not found.');
264
        }
265
266
        $product = Product::findById($model_id);
267
268
        $request = Yii::$app->request;
269
270
        $values_by_property_id = $request->get('properties', []);
271
        if (!is_array($values_by_property_id)) {
272
            $values_by_property_id = [$values_by_property_id];
273
        }
274
275
        $selected_category_id = $request->get('last_category_id');
276
277
        $selected_category_ids = $request->get('categories', []);
278
        if (!is_array($selected_category_ids)) {
279
            $selected_category_ids = [$selected_category_ids];
280
        }
281
282
        $category_group_id = intval($request->get('category_group_id', 0));
283
284
        // trigger that we are to show product to user!
285
        // wow! such product! very events!
286
        $specialEvent = new ProductPageShowed([
287
            'product_id' => $product->id,
288
        ]);
289
        EventTriggeringHelper::triggerSpecialEvent($specialEvent);
290
291 View Code Duplication
        if (!empty($product->meta_description)) {
292
            $this->view->registerMetaTag(
293
                [
294
                    'name' => 'description',
295
                    'content' => ContentBlockHelper::compileContentString(
296
                        $product->meta_description,
297
                        Product::className() . ":{$product->id}:meta_description",
298
                        new TagDependency(
299
                            [
300
                                'tags' => [
301
                                    ActiveRecordHelper::getCommonTag(ContentBlock::className()),
302
                                    ActiveRecordHelper::getCommonTag(Product::className())
303
                                ]
304
                            ]
305
                        )
306
                    )
307
                ],
308
                'meta_description'
309
            );
310
        }
311
312
        $selected_category = ($selected_category_id > 0) ? Category::findById($selected_category_id) : null;
313
314
        $this->view->title = $product->title;
315
        $this->view->blocks['h1'] = $product->h1;
316
        $this->view->blocks['announce'] = $product->announce;
317
        $this->view->blocks['content'] = $product->content;
318
        $this->view->blocks['title'] = $product->title;
319
320
321
        return $this->render(
322
            $this->computeViewFile($product, 'show'),
323
            [
324
                'model' => $product,
325
                'category_group_id' => $category_group_id,
326
                'values_by_property_id' => $values_by_property_id,
327
                'selected_category_id' => $selected_category_id,
328
                'selected_category' => $selected_category,
329
                'selected_category_ids' => $selected_category_ids,
330
                'object' => $object,
331
                'breadcrumbs' => $this->buildBreadcrumbsArray($selected_category, $product)
332
            ]
333
        );
334
    }
335
336
    /**
337
     * Search handler
338
     * @return array
339
     * @throws ForbiddenHttpException
340
     */
341
    public function actionSearch()
342
    {
343
        $headers = Yii::$app->response->getHeaders();
344
        $headers->set('X-Robots-Tag', 'none');
345
        $headers->set('X-Frame-Options', 'SAMEORIGIN');
346
        $headers->set('X-Content-Type-Options', 'nosniff');
347
        if (!Yii::$app->request->isAjax) {
348
            throw new ForbiddenHttpException();
349
        }
350
        $model = new Search();
351
        $model->load(Yii::$app->request->get());
352
        $cacheKey = 'ProductSearchIds: ' . $model->q;
353
        $ids = Yii::$app->cache->get($cacheKey);
354
        if ($ids === false) {
355
            $ids = ArrayHelper::merge(
356
                $model->searchProductsByDescription(),
357
                $model->searchProductsByProperty()
358
            );
359
            Yii::$app->cache->set(
360
                $cacheKey,
361
                $ids,
362
                86400,
363
                new TagDependency(
364
                    [
365
                        'tags' => ActiveRecordHelper::getCommonTag(Product::className()),
366
                    ]
367
                )
368
            );
369
        }
370
371
        /** @var \app\modules\shop\ShopModule $module */
372
        $module = Yii::$app->modules['shop'];
373
374
        $pages = new Pagination(
375
            [
376
                'defaultPageSize' => $module->searchResultsLimit,
377
                'forcePageParam' => false,
378
                'totalCount' => count($ids),
379
            ]
380
        );
381
        $cacheKey .= ' : ' . $pages->offset;
382
        $products = Yii::$app->cache->get($cacheKey);
383 View Code Duplication
        if ($products === false) {
384
            $products = Product::find()->where(
385
                [
386
                    'in',
387
                    '`id`',
388
                    array_slice(
389
                        $ids,
390
                        $pages->offset,
391
                        $pages->limit
392
                    )
393
                ]
394
            )->addOrderBy('sort_order')->with('images')->all();
395
            Yii::$app->cache->set(
396
                $cacheKey,
397
                $products,
398
                86400,
399
                new TagDependency(
400
                    [
401
                        'tags' => ActiveRecordHelper::getCommonTag(Product::className()),
402
                    ]
403
                )
404
            );
405
        }
406
        Yii::$app->response->format = Response::FORMAT_JSON;
407
        return [
408
            'view' => $this->renderAjax(
409
                'search',
410
                [
411
                    'model' => $model,
412
                    'pages' => $pages,
413
                    'products' => $products,
414
                ]
415
            ),
416
            'totalCount' => count($ids),
417
        ];
418
419
    }
420
421
    /**
422
    * This function build array for widget "Breadcrumbs"
423
    * @param Category $selCat - model of current category
424
    * @param Product|null $product - model of product, if current page is a page of product
425
    * @param array $properties - array of properties and static values
426
    * Return an array for widget or empty array
427
    */
428
    private function buildBreadcrumbsArray($selCat, $product = null, $properties = [])
429
    {
430
        if ($selCat === null) {
431
            return [];
432
        }
433
434
        // init
435
        $breadcrumbs = [];
436
        if ($product !== null) {
437
            $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...
438
        }
439
        $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...
440
441
        // get basic data
442
        $parent = $selCat->parent_id > 0 ? Category::findById($selCat->parent_id) : null;
443 View Code Duplication
        while ($parent !== null) {
444
            $crumbs[$parent->slug] = $parent->breadcrumbs_label;
445
            $parent = $parent->parent;
446
        }
447
448
        // build array for widget
449
        $url = '';
450
        $crumbs = array_reverse($crumbs, true);
451 View Code Duplication
        foreach ($crumbs as $slug => $label) {
452
            $url .= '/' . $slug;
453
            $breadcrumbs[] = [
454
                'label' => $label,
455
                'url' => $url
456
            ];
457
        }
458
        if (is_null($product) && $this->module->showFiltersInBreadcrumbs && !empty($properties)) {
459
            $route = [
460
                '@category',
461
                'last_category_id' => $selCat->id,
462
                'category_group_id' => $selCat->category_group_id,
463
            ];
464
            $params = [];
465
            foreach ($properties as $propertyId => $propertyStaticValues) {
466
                $localParams = $params;
467
                foreach ((array) $propertyStaticValues as $propertyStaticValue) {
468
                    $psv = PropertyStaticValues::findById($propertyStaticValue);
469
                    if (is_null($psv)) {
470
                        continue;
471
                    }
472
                    $localParams[$propertyId][] = $propertyStaticValue;
473
                    $breadcrumbs[] = [
474
                        'label' => $psv['name'],
475
                        'url' => array_merge($route, ['properties' => $localParams]),
476
                    ];
477
                }
478
                $params[$propertyId] = $propertyStaticValues;
479
            }
480
        }
481
        unset($breadcrumbs[count($breadcrumbs) - 1]['url']); // last item is not a link
482
483
        if (isset(Yii::$app->response->blocks['breadcrumbs_label'])) {
484
            // last item label rewrited through prefiltered page or something similar
485
            $breadcrumbs[count($breadcrumbs) - 1]['label'] = Yii::$app->response->blocks['breadcrumbs_label'];
486
        }
487
488
        return $breadcrumbs;
489
    }
490
}
491