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 — action_getCatTree_order ( 77944b )
by
unknown
23:56
created

BackendProductController   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 673
Duplicated Lines 22.29 %

Coupling/Cohesion

Components 1
Dependencies 29

Importance

Changes 5
Bugs 3 Features 1
Metric Value
wmc 80
lcom 1
cbo 29
dl 150
loc 673
rs 1.1138
c 5
b 3
f 1

12 Methods

Rating   Name   Duplication   Size   Complexity  
A actionAjaxRelatedProduct() 0 22 2
A behaviors() 0 14 1
C actions() 27 69 7
A actionIndex() 0 20 2
F actionEdit() 15 139 22
F actionGenerate() 34 132 16
C actionClone() 47 109 12
B actionDelete() 0 23 5
A actionRemoveAll() 0 12 3
A sortModels() 19 19 2
B generateOptions() 0 24 6
A generateCase() 8 8 2

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

1
<?php
2
3
namespace app\modules\shop\controllers;
4
5
use app\backend\actions\PropertyHandler;
6
use app\backend\components\BackendController;
7
use app\backend\events\BackendEntityEditEvent;
8
use app\modules\image\widgets\views\AddImageAction;
9
use app\modules\shop\models\Category;
10
use app\modules\image\models\Image;
11
use app\models\Object;
12
use app\models\ObjectPropertyGroup;
13
use app\modules\shop\models\Product;
14
use app\models\Property;
15
use app\models\PropertyStaticValues;
16
use app\models\ViewObject;
17
use app\properties\HasProperties;
18
use app\modules\image\widgets\RemoveAction;
19
use app\modules\image\widgets\SaveInfoAction;
20
use app\modules\image\widgets\UploadAction;
21
use app\backend\actions\UpdateEditable;
22
use devgroup\JsTreeWidget\AdjacencyFullTreeDataAction;
23
use Yii;
24
use yii\data\ActiveDataProvider;
25
use yii\db\Query;
26
use yii\filters\AccessControl;
27
use yii\helpers\ArrayHelper;
28
use yii\helpers\Json;
29
use yii\helpers\Url;
30
use yii\web\NotFoundHttpException;
31
use yii\web\ServerErrorHttpException;
32
33
class BackendProductController extends BackendController
34
{
35
    const EVENT_BACKEND_PRODUCT_EDIT = 'backend-product-edit';
36
    const EVENT_BACKEND_PRODUCT_EDIT_SAVE = 'backend-product-edit-save';
37
    const EVENT_BACKEND_PRODUCT_EDIT_FORM = 'backend-product-edit-form';
38
    const EVENT_BACKEND_PRODUCT_AFTER_SAVE = 'backend-product-after-save';
39
40
    /**
41
     * @inheritdoc
42
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array<strin...g,boolean|string[]>[]>>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
43
     */
44
    public function behaviors()
45
    {
46
        return [
47
            'access' => [
48
                'class' => AccessControl::className(),
49
                'rules' => [
50
                    [
51
                        'allow' => true,
52
                        'roles' => ['product manage'],
53
                    ],
54
                ],
55
            ],
56
        ];
57
    }
58
59
    /**
60
     * @inheritdoc
61
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
62
     */
63
    public function actions()
64
    {
65
        return [
66
            'getTree' => [
67
                'class' => AdjacencyFullTreeDataAction::className(),
68
                'class_name' => Category::className(),
69
                'model_label_attribute' => 'name',
70
            ],
71
            'getCatTree' => [
72
                'class' => 'app\backend\actions\JSSelectableTreeGetTree',
73
                'modelName' => 'app\modules\shop\models\Category',
74
                'label_attribute' => 'name',
75
                'vary_by_type_attribute' => null,
76
            ],
77
            'addImage' => [
78
                'class' => AddImageAction::className(),
79
            ],
80
            'upload' => [
81
                'class' => UploadAction::className(),
82
                'upload' => 'theme/resources/product-images',
83
            ],
84
            'remove' => [
85
                'class' => RemoveAction::className(),
86
                'uploadDir' => 'theme/resources/product-images',
87
            ],
88
            'save-info' => [
89
                'class' => SaveInfoAction::className(),
90
            ],
91
            'update-editable' => [
92
                'class' => UpdateEditable::className(),
93
                'modelName' => Product::className(),
94
                'allowedAttributes' => [
95 View Code Duplication
                    'currency_id' => function (Product $model, $attribute) {
0 ignored issues
show
Unused Code introduced by
The parameter $attribute is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
96
                        if ($model === null || $model->currency === null || $model->currency_id === 0) {
0 ignored issues
show
Bug introduced by
The property currency does not seem to exist. Did you mean currency_id?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
97
                            return null;
98
                        }
99
                        return \yii\helpers\Html::tag(
100
                            'div',
101
                            $model->currency->name,
0 ignored issues
show
Bug introduced by
The property currency does not seem to exist. Did you mean currency_id?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
102
                            ['class' => $model->currency->name]
0 ignored issues
show
Bug introduced by
The property currency does not seem to exist. Did you mean currency_id?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
103
                        );
104
                    },
105
                    'price',
106
                    'old_price',
107 View Code Duplication
                    'active' => function (Product $model) {
108
                        if ($model === null || $model->active === null) {
109
                            return null;
110
                        }
111
                        if ($model->active === 1) {
112
                            $label_class = 'label-success';
113
                            $value = 'Active';
114
                        } else {
115
                            $value = 'Inactive';
116
                            $label_class = 'label-default';
117
                        }
118
                        return \yii\helpers\Html::tag(
119
                            'span',
120
                            Yii::t('app', $value),
121
                            ['class' => "label $label_class"]
122
                        );
123
                    },
124
                ],
125
            ],
126
            'property-handler' => [
127
                'class' => PropertyHandler::className(),
128
                'modelName' => Product::className()
129
            ]
130
        ];
131
    }
132
133
    /**
134
     * @return string
135
     * @throws ServerErrorHttpException
136
     */
137
    public function actionIndex()
138
    {
139
        $searchModel = new Product();
140
        $params = Yii::$app->request->get();
141
        /** @var ActiveDataProvider $dataProvider */
142
        $dataProvider = $searchModel->search($params);
143
        if (null !== $catId = Yii::$app->request->get('parent_id')) {
144
            $dataProvider->query->leftJoin(
145
                Object::getForClass(Product::className())->categories_table_name . ' cp',
146
                'cp.object_model_id=product.id'
147
            )->andWhere('product.parent_id=0 AND cp.category_id=:cur', [':cur' => $catId]);
148
        }
149
        return $this->render(
150
            'index',
151
            [
152
                'dataProvider' => $dataProvider,
153
                'searchModel' => $searchModel,
154
            ]
155
        );
156
    }
157
158
    /**
159
     * @param null $id
160
     * @return string|\yii\web\Response
161
     * @throws NotFoundHttpException
162
     * @throws ServerErrorHttpException
163
     * @throws \Exception
164
     * @throws \yii\base\InvalidRouteException
165
     */
166
    public function actionEdit($id = null)
167
    {
168
        /*
169
         * @todo Продумать механизм сохранения изображений для нового продукта.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
170
         * Сейчас для нового продукта скрывается форма добавления изображений.
171
         */
172
        if (null === $object = Object::getForClass(Product::className())) {
173
            throw new ServerErrorHttpException;
174
        }
175
176
        /** @var null|Product|HasProperties|\devgroup\TagDependencyHelper\ActiveRecordHelper $model */
177
        $model = null;
178
        $parent = null;
179
        if (null === $id) {
180
            $model = new Product();
181
            $model->loadDefaultValues();
182
            $parent_id = Yii::$app->request->get('owner_id', 0);
183 View Code Duplication
            if (0 !== intval($parent_id) && null !== Product::findById($parent_id)) {
184
                $model->parent_id = $parent_id;
185
            }
186
            $model->measure_id = $this->module->defaultMeasureId;
187
        } else {
188
            $model = Product::findById($id, null);
189 View Code Duplication
            if ((null !== $model) && ($model->parent_id > 0)) {
190
                $parent = Product::findById($model->parent_id, null);
191
            }
192
        }
193
194
        if (null === $model) {
195
            throw new NotFoundHttpException();
196
        }
197
198
        $model->loadRelatedProductsArray();
199
200
        $event = new BackendEntityEditEvent($model);
201
        $this->trigger(self::EVENT_BACKEND_PRODUCT_EDIT, $event);
202
203
        $post = \Yii::$app->request->post();
204
205
        if ($event->isValid && $model->load($post)) {
206
            $saveStateEvent = new BackendEntityEditEvent($model);
207
            $this->trigger(self::EVENT_BACKEND_PRODUCT_EDIT_SAVE, $saveStateEvent);
208
209
            if ($model->validate()) {
210
                if (isset($post['GeneratePropertyValue'])) {
211
                    $generateValues = $post['GeneratePropertyValue'];
212
                } else {
213
                    $generateValues = [];
214
                }
215
                if (isset($post['PropertyGroup'])) {
216
                    $model->option_generate = Json::encode(
0 ignored issues
show
Documentation introduced by
The property option_generate does not exist on object<app\modules\shop\models\Product>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
217
                        [
218
                            'group' => $post['PropertyGroup']['id'],
219
                            'values' => $generateValues
220
                        ]
221
                    );
222
                } else {
223
                    $model->option_generate = '';
0 ignored issues
show
Documentation introduced by
The property option_generate does not exist on object<app\modules\shop\models\Product>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
224
                }
225
226
                $save_result = $model->save();
227
                $model->saveProperties($post);
0 ignored issues
show
Documentation Bug introduced by
The method saveProperties does not exist on object<app\modules\shop\models\Product>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
228
                $model->saveRelatedProducts();
229
230 View Code Duplication
                if (null !== $view_object = ViewObject::getByModel($model, true)) {
231
                    if ($view_object->load($post, 'ViewObject')) {
232
                        if ($view_object->view_id <= 0) {
233
                            $view_object->delete();
234
                        } else {
235
                            $view_object->save();
236
                        }
237
                    }
238
                }
239
240
                if ($save_result) {
241
                    $modelAfterSaveEvent = new BackendEntityEditEvent($model);
242
                    $this->trigger(self::EVENT_BACKEND_PRODUCT_AFTER_SAVE, $modelAfterSaveEvent);
243
244
                    $categories = isset($post['Product']['categories']) ? $post['Product']['categories'] : [];
245
246
                    $model->saveCategoriesBindings($categories);
247
248
                    $this->runAction('save-info', ['model_id'=>$model->id]);
249
                    $model->invalidateTags();
0 ignored issues
show
Bug introduced by
The method invalidateTags() does not exist on app\modules\shop\models\Product. Did you maybe mean validate()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
250
251
252
                    $action = Yii::$app->request->post('action', 'save');
253
                    if (Yii::$app->request->post(HasProperties::FIELD_ADD_PROPERTY_GROUP)
254
                        || Yii::$app->request->post(HasProperties::FIELD_REMOVE_PROPERTY_GROUP)) {
255
                        $action = 'save';
256
                    }
257
                    $returnUrl = Yii::$app->request->get('returnUrl', ['index']);
258
                    switch ($action) {
259
                        case 'next':
260
                            return $this->redirect(
261
                                [
262
                                    'edit',
263
                                    'returnUrl' => $returnUrl,
264
                                    'parent_id' => Yii::$app->request->get('parent_id', null)
265
                                ]
266
                            );
267
                        case 'back':
268
                            return $this->redirect($returnUrl);
269
                        default:
270
                            return $this->redirect(
271
                                Url::toRoute([
272
                                    'edit',
273
                                    'id' => $model->id,
274
                                    'returnUrl' => $returnUrl,
275
                                    'parent_id' => $model->main_category_id
276
                                ])
277
                            );
278
                    }
279
                } else {
280
                    Yii::$app->session->setFlash('error', Yii::t('app', 'Cannot save data'));
281
                }
282
            } else {
283
                Yii::$app->session->setFlash('error', Yii::t('app', 'Cannot save data'));
284
            }
285
        }
286
287
        $items = ArrayHelper::map(
288
            Category::find()->all(),
289
            'id',
290
            'name'
291
        );
292
293
294
        return $this->render(
295
            'product-form',
296
            [
297
                'object' => $object,
298
                'model' => $model,
299
                'items' => $items,
300
                'selected' => $model->getCategoryIds(),
301
                'parent' => $parent,
302
            ]
303
        );
304
    }
305
306
    /**
307
     * @param $id
308
     * @throws NotFoundHttpException
309
     * @throws \yii\db\Exception
310
     */
311
    public function actionGenerate($id)
312
    {
313
        $post = \Yii::$app->request->post();
314
        if (!isset($post['GeneratePropertyValue'])) {
315
            throw new NotFoundHttpException();
316
        }
317
        $parent = Product::findById($id, null);
318
        if ($parent === null) {
319
            throw new NotFoundHttpException();
320
        }
321
322
        $object = Object::getForClass(Product::className());
323
        $catIds = (new Query())->select('category_id')->from([$object->categories_table_name])->where(
324
            'object_model_id = :id',
325
            [':id' => $id]
326
        )->orderBy(['sort_order' => SORT_ASC, 'id' => SORT_ASC])->column();
327
328
329
        if (isset($post['GeneratePropertyValue'])) {
330
            $generateValues = $post['GeneratePropertyValue'];
331
            $post[HasProperties::FIELD_ADD_PROPERTY_GROUP]['Product'] = $post['PropertyGroup']['id'];
332
        } else {
333
            $generateValues = [];
334
        }
335
        $parent->option_generate = Json::encode(
0 ignored issues
show
Documentation introduced by
The property option_generate does not exist on object<app\modules\shop\models\Product>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
336
            [
337
                'group' => $post['PropertyGroup']['id'],
338
                'values' => $generateValues
339
            ]
340
        );
341
        $parent->save();
342
343
        $postProperty = [];
344
        foreach ($post['GeneratePropertyValue'] as $key_property => $values) {
345
            $inner = [];
346
            foreach ($values as $key_value => $trash) {
347
                $inner[] = [$key_property => $key_value];
348
            }
349
            $postProperty[] = $inner;
350
        }
351
352
        $optionProperty = self::generateOptions($postProperty);
353
354
        foreach ($optionProperty as $option) {
355
            /** @var Product|HasProperties $model */
356
            $model = new Product;
357
            $model->load($post);
0 ignored issues
show
Bug introduced by
The method load does only exist in app\modules\shop\models\Product, but not in app\properties\HasProperties.

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...
358
359
            $model->parent_id = $parent->id;
360
            $nameAppend = [];
361
            $slugAppend = [];
362
            $tempPost = [];
363
364
            // @todo something
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
365
            foreach ($option as $optionValue) {
366
                $valueModels = [];
367
                foreach ($optionValue as $propertyKey => $propertyValue) {
368
                    if (false === in_array($propertyKey, $valueModels)) {
369
                        /** @var  $propertyStaticValues  PropertyStaticValues */
370
                        $propertyStaticValues = PropertyStaticValues::findOne($propertyValue);
371
                        $key = $propertyStaticValues->property->key;
372
                        $tempPost[$key] = $propertyValue;
373
                        $valueModels[] = $propertyKey;
374
                        $nameAppend[] = $propertyStaticValues->name;
375
                        $slugAppend[] = $propertyStaticValues->id;
376
                    }
377
                }
378
            }
379
            $model->measure_id = $parent->measure_id;
380
            $model->name = $parent->name . ' (' . implode(', ', $nameAppend) . ')';
381
            $model->slug = $parent->slug . '-' . implode('-', $slugAppend);
382
            $save_model = $model->save();
0 ignored issues
show
Bug introduced by
The method save does only exist in app\modules\shop\models\Product, but not in app\properties\HasProperties.

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...
383
            if ($save_model) {
384
                foreach (array_keys($parent->propertyGroups) as $key) {
0 ignored issues
show
Documentation introduced by
The property propertyGroups does not exist on object<app\modules\shop\models\Product>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
385
                    $opg = new ObjectPropertyGroup();
386
                    $opg->attributes = [
387
                        'object_id' => $parent->object->id,
0 ignored issues
show
Documentation introduced by
The property object does not exist on object<app\modules\shop\models\Product>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
388
                        'object_model_id' => $model->id,
389
                        'property_group_id' => $key,
390
                    ];
391
                    $opg->save();
392
                }
393
                $newValues = array_merge($parent->abstractModel->attributes, $tempPost);
0 ignored issues
show
Documentation introduced by
The property abstractModel does not exist on object<app\modules\shop\models\Product>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
394
                $model->saveProperties([
0 ignored issues
show
Bug introduced by
The method saveProperties does only exist in app\properties\HasProperties, but not in app\modules\shop\models\Product.

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...
395
                    'Properties_Product_' . $model->id => $newValues,
396
                ]);
397
                $add = [];
398
                foreach ($catIds as $value) {
399
                    $add[] = [
400
                        $value,
401
                        $model->id
402
                    ];
403
                }
404 View Code Duplication
                if (!empty($add)) {
405
                    Yii::$app->db->createCommand()->batchInsert(
406
                        $object->categories_table_name,
407
                        ['category_id', 'object_model_id'],
408
                        $add
409
                    )->execute();
410
                }
411
412
                $params = $parent->images;
0 ignored issues
show
Documentation introduced by
The property images does not exist on object<app\modules\shop\models\Product>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
413 View Code Duplication
                if (!empty($params)) {
414
                    $rows = [];
415
                    foreach ($params as $param) {
416
                        $rows[] = [
417
                            $param['object_id'],
418
                            $model->id,
419
                            $param['filename'],
420
                            $param['image_title'],
421
                            $param['image_alt'],
422
                            $param['sort_order'],
423
                        ];
424
                    }
425
426
                    Yii::$app->db->createCommand()->batchInsert(
427
                        Image::tableName(),
428
                        [
429
                            'object_id',
430
                            'object_model_id',
431
                            'filename',
432
                            'image_title',
433
                            'image_alt',
434
                            'sort_order',
435
                        ],
436
                        $rows
437
                    )->execute();
438
439
                }
440
            }
441
        }
442
    }
443
444
    /**
445
     * Clone product action.
446
     * @param integer $id
447
     * @throws \yii\web\NotFoundHttpException
448
     */
449
    public function actionClone($id, $returnUrl = ['index'])
450
    {
451
        /** @var Product|HasProperties $model */
452
        $model = Product::findOne($id);
453
        if ($model === null) {
454
            throw new NotFoundHttpException;
455
        }
456
457
        /** @var Product|HasProperties $newModel */
458
        $newModel = new Product;
459
        $newModel->setAttributes($model->attributes, false);
0 ignored issues
show
Bug introduced by
The method setAttributes does only exist in app\modules\shop\models\Product, but not in app\properties\HasProperties.

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...
460
        $time = time();
461
        $newModel->name .= ' (copy ' . date('Y-m-d h:i:s', $time) . ')';
462
        $newModel->slug .= '-copy-' . date('Ymdhis', $time);
463
        $newModel->id = null;
464 View Code Duplication
        if ($newModel->validate() === false) {
0 ignored issues
show
Bug introduced by
The method validate does only exist in app\modules\shop\models\Product, but not in app\properties\HasProperties.

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...
465
            $newModel->slug = substr(uniqid() . "-" . $model->slug, 0, 80);
466
        }
467
        if ($newModel->save()) {
0 ignored issues
show
Bug introduced by
The method save does only exist in app\modules\shop\models\Product, but not in app\properties\HasProperties.

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...
468
            $object = Object::getForClass(get_class($newModel));
469
            // save categories
470
            $categoriesTableName = Object::getForClass(Product::className())->categories_table_name;
471
            $query = new Query();
472
            $params = $query->select(['category_id', 'sort_order'])->from($categoriesTableName)->where(
473
                ['object_model_id' => $model->id]
474
            )->all();
475
            if (!empty($params)) {
476
                $rows = [];
477
                foreach ($params as $param) {
478
                    $rows[] = [
479
                        $param['category_id'],
480
                        $newModel->id,
481
                        $param['sort_order'],
482
                    ];
483
                }
484
                Yii::$app->db->createCommand()->batchInsert(
485
                    $categoriesTableName,
486
                    [
487
                        'category_id',
488
                        'object_model_id',
489
                        'sort_order'
490
                    ],
491
                    $rows
492
                )->execute();
493
            }
494
495
            // save images bindings
496
            $params = $query->select(
497
                ['object_id', 'filename', 'image_title', 'image_alt', 'sort_order']
498
            )->from(Image::tableName())->where(
499
                [
500
                    'object_id' => $object->id,
501
                    'object_model_id' => $model->id
502
                ]
503
            )->all();
504 View Code Duplication
            if (!empty($params)) {
505
                $rows = [];
506
                foreach ($params as $param) {
507
                    $rows[] = [
508
                        $param['object_id'],
509
                        $newModel->id,
510
                        $param['filename'],
511
                        $param['image_title'],
512
                        $param['image_alt'],
513
                        $param['sort_order'],
514
                    ];
515
                }
516
                Yii::$app->db->createCommand()->batchInsert(
517
                    Image::tableName(),
518
                    [
519
                        'object_id',
520
                        'object_model_id',
521
                        'filename',
522
                        'image_title',
523
                        'image_alt',
524
                        'sort_order',
525
                    ],
526
                    $rows
527
                )->execute();
528
            }
529
            $newModelProps = [];
530 View Code Duplication
            foreach (array_keys($model->propertyGroups) as $key) {
531
                $opg = new ObjectPropertyGroup();
532
                $opg->attributes = [
533
                    'object_id' => $object->id,
534
                    'object_model_id' => $newModel->id,
535
                    'property_group_id' => $key,
536
                ];
537
                $opg->save();
538
                $props = Property::getForGroupId($key);
539
                foreach ($props as $prop) {
0 ignored issues
show
Bug introduced by
The expression $props of type null|array<integer,object<app\models\Property>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
540
                    $propValues = $model->getPropertyValuesByPropertyId($prop->id);
0 ignored issues
show
Bug introduced by
The method getPropertyValuesByPropertyId does only exist in app\properties\HasProperties, but not in app\modules\shop\models\Product.

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...
541
                    if ($propValues !== null) {
542
                        foreach ($propValues->values as $val) {
543
                            $valueToSave = ArrayHelper::getValue($val, 'psv_id', $val['value']);
544
                            $newModelProps[$prop->key][] = $valueToSave;
545
                        }
546
                    }
547
                }
548
            }
549
            $newModel->saveProperties(
0 ignored issues
show
Bug introduced by
The method saveProperties does only exist in app\properties\HasProperties, but not in app\modules\shop\models\Product.

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...
550
                [
551
                    'Properties_Product_' . $newModel->id => $newModelProps,
552
                ]
553
            );
554
            Yii::$app->session->setFlash('success', Yii::t('app', 'Product has been cloned successfully.'));
555
            $this->redirect(['edit', 'id' => $newModel->id, 'returnUrl' => $returnUrl]);
556
        }
557
    }
558
559
    /**
560
     * @param $id
561
     * @return \yii\web\Response
562
     * @throws NotFoundHttpException
563
     * @throws \Exception
564
     */
565
    public function actionDelete($id)
566
    {
567
        /** @var Product $model */
568
        if (null === $model = Product::findOne($id)) {
569
            throw new NotFoundHttpException;
570
        }
571
572
        if (Yii::$app->request->get('returnUrl') !== null) {
573
            $redirect = Yii::$app->request->get('returnUrl');
574
        } elseif ($model->parent_id == 0) {
575
            $redirect = Url::toRoute(['index']);
576
        } else {
577
            $redirect = Url::toRoute(['edit', 'id' => $model->parent_id]);
578
        }
579
580
        if (!$model->delete()) {
0 ignored issues
show
Bug introduced by
The method delete cannot be called on $model (of type array|boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
581
            Yii::$app->session->setFlash('info', Yii::t('app', 'The object is placed in the cart'));
582
        } else {
583
            Yii::$app->session->setFlash('info', Yii::t('app', 'Object has been removed'));
584
        }
585
586
        return $this->redirect($redirect);
587
    }
588
589
    /**
590
     * @param $parent_id
591
     * @return \yii\web\Response
592
     * @throws \Exception
593
     */
594
    public function actionRemoveAll($parent_id)
595
    {
596
        $items = Yii::$app->request->post('items', []);
597
        if (!empty($items)) {
598
            $items = Product::find()->where(['in', 'id', $items])->all();
599
            foreach ($items as $item) {
600
                $item->delete();
601
            }
602
        }
603
604
        return $this->redirect(['index', 'parent_id' => $parent_id]);
605
    }
606
607
    /**
608
     * @param $tableName
609
     * @param $ids
610
     * @param string $field
611
     * @return bool
612
     * @throws \yii\db\Exception
613
     */
614 View Code Duplication
    public static function sortModels($tableName, $ids, $field = 'sort_order')
615
    {
616
        $priorities = [];
617
        $start = 0;
618
        $ids_sorted = $ids;
619
        sort($ids_sorted);
620
        foreach ($ids as $id) {
621
            $priorities[$id] = $ids_sorted[$start++];
622
        }
623
        $sql = "UPDATE " . $tableName . " SET $field = " . self::generateCase($priorities) . " WHERE id IN(" . implode(
624
                ', ',
625
                $ids
626
            ) . ")";
627
628
        return Yii::$app->db->createCommand(
629
            $sql
630
        )->execute() > 0;
631
632
    }
633
634
    /**
635
     * Рекурсивный генератор свойств для создания комплектаций.
636
     * @param array $array Трехмерный массив вида:
637
     * [[['{property1}' => '{value1}']], [['{property1}' => '{value2}']], [['{property2}' => '{value1}']], [['{property2}' => '{value1}']]]
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 139 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
638
     * @param array $result Используется для передачи результатов внутри рекурсии
639
     * @param integer $count Счетчик внутри рекурсии
640
     * @return array
641
     */
642
    public static function generateOptions($array, $result = [], $count = 0)
643
    {
644
        if (empty($result)) {
645
            foreach ($array[$count] as $value) {
646
                $result[] = [$value];
647
            }
648
            $count++;
649
            $arResult = self::generateOptions($array, $result, $count);
650
        } else {
651
            if (isset($array[$count])) {
652
                $nextResult = [];
653
                foreach ($array[$count] as $value) {
654
                    foreach ($result as $resValue) {
655
                        $nextResult[] = array_merge($resValue, [$value]);
656
                    }
657
                }
658
                $count++;
659
                $arResult = self::generateOptions($array, $nextResult, $count);
660
            } else {
661
                return $result;
662
            }
663
        }
664
        return $arResult;
665
    }
666
667
    /**
668
     * @param $priorities
669
     * @return string
670
     */
671 View Code Duplication
    private static function generateCase($priorities)
672
    {
673
        $result = 'CASE `id`';
674
        foreach ($priorities as $k => $v) {
675
            $result .= ' when "' . $k . '" then "' . $v . '"';
676
        }
677
        return $result . ' END';
678
    }
679
680
    /**
681
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,false|array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
682
     */
683
    public function actionAjaxRelatedProduct()
684
    {
685
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
686
687
        $result = [
688
            'more' => false,
689
            'results' => []
690
        ];
691
        $search = Yii::$app->request->get('search');
692
        if (!empty($search['term'])) {
693
            $query = new \yii\db\Query();
694
            $query->select('id, name AS text')->from(Product::tableName())->andWhere(
695
                ['like', 'name', $search['term']]
696
            )->andWhere(['active' => 1])->orderBy(['sort_order' => SORT_ASC, 'name' => SORT_ASC]);
697
            $command = $query->createCommand();
698
            $data = $command->queryAll();
699
700
            $result['results'] = array_values($data);
701
        }
702
703
        return $result;
704
    }
705
}