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 — master ( e91cf6...3a6b3f )
by Alexander
10:43
created

BackendProductController::actionRemoveAll()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 12
rs 9.4285
cc 3
eloc 7
nc 2
nop 1
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\Currency;
14
use app\modules\shop\models\Product;
15
use app\models\Property;
16
use app\models\PropertyStaticValues;
17
use app\models\ViewObject;
18
use app\properties\HasProperties;
19
use app\modules\image\widgets\RemoveAction;
20
use app\modules\image\widgets\SaveInfoAction;
21
use app\modules\image\widgets\UploadAction;
22
use app\backend\actions\UpdateEditable;
23
use devgroup\JsTreeWidget\AdjacencyFullTreeDataAction;
24
use Yii;
25
use yii\data\ActiveDataProvider;
26
use yii\db\Query;
27
use yii\filters\AccessControl;
28
use yii\helpers\ArrayHelper;
29
use yii\helpers\Json;
30
use yii\helpers\Url;
31
use yii\web\NotFoundHttpException;
32
use yii\web\ServerErrorHttpException;
33
use app\backend\actions\MassPublishAction;
34
use app\backend\actions\CategoryMovementsAction;
35
36
class BackendProductController extends BackendController
37
{
38
    const EVENT_BACKEND_PRODUCT_EDIT = 'backend-product-edit';
39
    const EVENT_BACKEND_PRODUCT_EDIT_SAVE = 'backend-product-edit-save';
40
    const EVENT_BACKEND_PRODUCT_EDIT_FORM = 'backend-product-edit-form';
41
    const EVENT_BACKEND_PRODUCT_AFTER_SAVE = 'backend-product-after-save';
42
43
    /**
44
     * @inheritdoc
45
     * @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...
46
     */
47 View Code Duplication
    public function behaviors()
48
    {
49
        return [
50
            'access' => [
51
                'class' => AccessControl::className(),
52
                'rules' => [
53
                    [
54
                        'allow' => true,
55
                        'roles' => ['product manage'],
56
                    ],
57
                ],
58
            ],
59
        ];
60
    }
61
62
    /**
63
     * @inheritdoc
64
     * @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...
65
     */
66
    public function actions()
67
    {
68
        return [
69
            'getTree' => [
70
                'class' => AdjacencyFullTreeDataAction::className(),
71
                'class_name' => Category::className(),
72
                'model_label_attribute' => 'name',
73
            ],
74
            'getCatTree' => [
75
                'class' => 'app\backend\actions\JSSelectableTreeGetTree',
76
                'modelName' => 'app\modules\shop\models\Category',
77
                'label_attribute' => 'name',
78
                'vary_by_type_attribute' => null,
79
            ],
80
            'addImage' => [
81
                'class' => AddImageAction::className(),
82
            ],
83
            'upload' => [
84
                'class' => UploadAction::className(),
85
                'upload' => 'theme/resources/product-images',
86
            ],
87
            'remove' => [
88
                'class' => RemoveAction::className(),
89
                'uploadDir' => 'theme/resources/product-images',
90
            ],
91
            'save-info' => [
92
                'class' => SaveInfoAction::className(),
93
            ],
94
            'update-editable' => [
95
                'class' => UpdateEditable::className(),
96
                'modelName' => Product::className(),
97
                'allowedAttributes' => [
98 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...
99
                        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...
100
                            return null;
101
                        }
102
                        return \yii\helpers\Html::tag(
103
                            'div',
104
                            $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...
105
                            ['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...
106
                        );
107
                    },
108
                    'price',
109
                    'old_price',
110 View Code Duplication
                    'active' => function (Product $model) {
111
                        if ($model === null || $model->active === null) {
112
                            return null;
113
                        }
114
                        if ($model->active === 1) {
115
                            $label_class = 'label-success';
116
                            $value = 'Active';
117
                        } else {
118
                            $value = 'Inactive';
119
                            $label_class = 'label-default';
120
                        }
121
                        return \yii\helpers\Html::tag(
122
                            'span',
123
                            Yii::t('app', $value),
124
                            ['class' => "label $label_class"]
125
                        );
126
                    },
127
                ],
128
            ],
129
            'property-handler' => [
130
                'class' => PropertyHandler::className(),
131
                'modelName' => Product::className()
132
            ],
133
            'publish-switch' => [
134
                'class' => MassPublishAction::className(),
135
                'modelName' => Product::className(),
136
                'attribute' => 'active',
137
            ],
138
            'categoryMovements' => [
139
                'class' => CategoryMovementsAction::className(),
140
            ]
141
        ];
142
    }
143
144
    /**
145
     * @return string
146
     * @throws ServerErrorHttpException
147
     */
148
    public function actionIndex()
149
    {
150
        $searchModel = new Product();
151
        $params = Yii::$app->request->get();
152
        /** @var ActiveDataProvider $dataProvider */
153
        $dataProvider = $searchModel->search($params);
154
        if (null !== $catId = Yii::$app->request->get('parent_id')) {
155
            $dataProvider->query->leftJoin(
156
                Object::getForClass(Product::className())->categories_table_name . ' cp',
157
                'cp.object_model_id=product.id'
158
            )->andWhere('product.parent_id=0 AND cp.category_id=:cur', [':cur' => $catId]);
159
        }
160
        return $this->render(
161
            'index',
162
            [
163
                'dataProvider' => $dataProvider,
164
                'searchModel' => $searchModel,
165
            ]
166
        );
167
    }
168
169
    /**
170
     * @param null $id
171
     * @return string|\yii\web\Response
172
     * @throws NotFoundHttpException
173
     * @throws ServerErrorHttpException
174
     * @throws \Exception
175
     * @throws \yii\base\InvalidRouteException
176
     */
177
    public function actionEdit($id = null)
178
    {
179
        /*
180
         * @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...
181
         * Сейчас для нового продукта скрывается форма добавления изображений.
182
         */
183
        if (null === $object = Object::getForClass(Product::className())) {
184
            throw new ServerErrorHttpException;
185
        }
186
187
        /** @var null|Product|HasProperties|\devgroup\TagDependencyHelper\ActiveRecordHelper $model */
188
        $model = null;
189
        $parent = null;
190
        if (null === $id) {
191
            $model = new Product();
192
            $model->loadDefaultValues();
193
            $model->currency_id = Currency::getMainCurrency()->id;
194
            $parent_id = Yii::$app->request->get('owner_id', 0);
195 View Code Duplication
            if (0 !== intval($parent_id) && null !== Product::findById($parent_id)) {
196
                $model->parent_id = $parent_id;
197
            }
198
            $model->measure_id = $this->module->defaultMeasureId;
199
        } else {
200
            $model = Product::findById($id, null);
201 View Code Duplication
            if ((null !== $model) && ($model->parent_id > 0)) {
202
                $parent = Product::findById($model->parent_id, null);
203
            }
204
        }
205
206
        if (null === $model) {
207
            throw new NotFoundHttpException();
208
        }
209
210
        $model->loadRelatedProductsArray();
211
212
        $event = new BackendEntityEditEvent($model);
213
        $this->trigger(self::EVENT_BACKEND_PRODUCT_EDIT, $event);
214
215
        $post = \Yii::$app->request->post();
216
217
        if ($event->isValid && $model->load($post)) {
218
            $saveStateEvent = new BackendEntityEditEvent($model);
219
            $this->trigger(self::EVENT_BACKEND_PRODUCT_EDIT_SAVE, $saveStateEvent);
220
221
            if ($model->validate()) {
222
                if (isset($post['GeneratePropertyValue'])) {
223
                    $generateValues = $post['GeneratePropertyValue'];
224
                } else {
225
                    $generateValues = [];
226
                }
227
                if (isset($post['PropertyGroup'])) {
228
                    $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...
229
                        [
230
                            'group' => $post['PropertyGroup']['id'],
231
                            'values' => $generateValues
232
                        ]
233
                    );
234
                } else {
235
                    $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...
236
                }
237
238
                $save_result = $model->save();
239
                $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...
240
                $model->saveRelatedProducts();
241
242 View Code Duplication
                if (null !== $view_object = ViewObject::getByModel($model, true)) {
243
                    if ($view_object->load($post, 'ViewObject')) {
244
                        if ($view_object->view_id <= 0) {
245
                            $view_object->delete();
246
                        } else {
247
                            $view_object->save();
248
                        }
249
                    }
250
                }
251
252
                if ($save_result) {
253
                    $modelAfterSaveEvent = new BackendEntityEditEvent($model);
254
                    $this->trigger(self::EVENT_BACKEND_PRODUCT_AFTER_SAVE, $modelAfterSaveEvent);
255
256
                    $categories = isset($post['Product']['categories']) ? $post['Product']['categories'] : [];
257
258
                    $model->saveCategoriesBindings($categories);
259
260
                    $this->runAction('save-info', ['model_id'=>$model->id]);
261
                    $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...
262
263
264
                    $action = Yii::$app->request->post('action', 'save');
265
                    if (Yii::$app->request->post(HasProperties::FIELD_ADD_PROPERTY_GROUP)
266
                        || Yii::$app->request->post(HasProperties::FIELD_REMOVE_PROPERTY_GROUP)) {
267
                        $action = 'save';
268
                    }
269
                    $returnUrl = Yii::$app->request->get('returnUrl', ['index']);
270
                    switch ($action) {
271
                        case 'next':
272
                            return $this->redirect(
273
                                [
274
                                    'edit',
275
                                    'returnUrl' => $returnUrl,
276
                                    'parent_id' => Yii::$app->request->get('parent_id', null)
277
                                ]
278
                            );
279
                        case 'back':
280
                            return $this->redirect($returnUrl);
281
                        default:
282
                            return $this->redirect(
283
                                Url::toRoute([
284
                                    'edit',
285
                                    'id' => $model->id,
286
                                    'returnUrl' => $returnUrl,
287
                                    'parent_id' => $model->main_category_id
288
                                ])
289
                            );
290
                    }
291
                } else {
292
                    Yii::$app->session->setFlash('error', Yii::t('app', 'Cannot save data'));
293
                }
294
            } else {
295
                Yii::$app->session->setFlash('error', Yii::t('app', 'Cannot save data'));
296
            }
297
        }
298
299
        $items = ArrayHelper::map(
300
            Category::find()->all(),
301
            'id',
302
            'name'
303
        );
304
305
306
        return $this->render(
307
            'product-form',
308
            [
309
                'object' => $object,
310
                'model' => $model,
311
                'items' => $items,
312
                'selected' => $model->getCategoryIds(),
313
                'parent' => $parent,
314
            ]
315
        );
316
    }
317
318
    /**
319
     * @param $id
320
     * @throws NotFoundHttpException
321
     * @throws \yii\db\Exception
322
     */
323
    public function actionGenerate($id)
324
    {
325
        $post = \Yii::$app->request->post();
326
        if (!isset($post['GeneratePropertyValue'])) {
327
            throw new NotFoundHttpException();
328
        }
329
        $parent = Product::findById($id, null);
330
        if ($parent === null) {
331
            throw new NotFoundHttpException();
332
        }
333
334
        $object = Object::getForClass(Product::className());
335
        $catIds = (new Query())->select('category_id')->from([$object->categories_table_name])->where(
336
            'object_model_id = :id',
337
            [':id' => $id]
338
        )->orderBy(['sort_order' => SORT_ASC, 'id' => SORT_ASC])->column();
339
340
341
        if (isset($post['GeneratePropertyValue'])) {
342
            $generateValues = $post['GeneratePropertyValue'];
343
            $post[HasProperties::FIELD_ADD_PROPERTY_GROUP]['Product'] = $post['PropertyGroup']['id'];
344
        } else {
345
            $generateValues = [];
346
        }
347
        $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...
348
            [
349
                'group' => $post['PropertyGroup']['id'],
350
                'values' => $generateValues
351
            ]
352
        );
353
        $parent->save();
354
355
        $postProperty = [];
356
        foreach ($post['GeneratePropertyValue'] as $key_property => $values) {
357
            $inner = [];
358
            foreach ($values as $key_value => $trash) {
359
                $inner[] = [$key_property => $key_value];
360
            }
361
            $postProperty[] = $inner;
362
        }
363
364
        $optionProperty = self::generateOptions($postProperty);
365
366
        foreach ($optionProperty as $option) {
367
            /** @var Product|HasProperties $model */
368
            $model = new Product;
369
            $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...
370
371
            $model->parent_id = $parent->id;
372
            $nameAppend = [];
373
            $slugAppend = [];
374
            $tempPost = [];
375
376
            // @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...
377
            foreach ($option as $optionValue) {
378
                $valueModels = [];
379
                foreach ($optionValue as $propertyKey => $propertyValue) {
380
                    if (false === in_array($propertyKey, $valueModels)) {
381
                        /** @var  $propertyStaticValues  PropertyStaticValues */
382
                        $propertyStaticValues = PropertyStaticValues::findOne($propertyValue);
383
                        $key = $propertyStaticValues->property->key;
384
                        $tempPost[$key] = $propertyValue;
385
                        $valueModels[] = $propertyKey;
386
                        $nameAppend[] = $propertyStaticValues->name;
387
                        $slugAppend[] = $propertyStaticValues->id;
388
                    }
389
                }
390
            }
391
            $model->measure_id = $parent->measure_id;
392
            $model->name = $parent->name . ' (' . implode(', ', $nameAppend) . ')';
393
            $model->slug = $parent->slug . '-' . implode('-', $slugAppend);
394
            $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...
395
            if ($save_model) {
396
                $groups = [
397
                    [$parent->object->id, $model->id, $post['PropertyGroup']['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...
398
                ];
399
                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...
400
                    if ($key === $post['PropertyGroup']['id']) {
401
                        continue;
402
                    }
403
                    $groups[] = [$parent->object->id, $model->id, $key];
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...
404
                }
405
                Yii::$app->db->createCommand()->batchInsert(
406
                    ObjectPropertyGroup::tableName(),
407
                    ['object_id', 'object_model_id', 'property_group_id'],
408
                    $groups
409
                )->execute();
410
                $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...
411
                $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...
412
                    'Properties_Product_' . $model->id => $newValues,
413
                ]);
414
                $add = [];
415
                foreach ($catIds as $value) {
416
                    $add[] = [
417
                        $value,
418
                        $model->id
419
                    ];
420
                }
421 View Code Duplication
                if (!empty($add)) {
422
                    Yii::$app->db->createCommand()->batchInsert(
423
                        $object->categories_table_name,
424
                        ['category_id', 'object_model_id'],
425
                        $add
426
                    )->execute();
427
                }
428
429
                $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...
430 View Code Duplication
                if (!empty($params)) {
431
                    $rows = [];
432
                    foreach ($params as $param) {
433
                        $rows[] = [
434
                            $param['object_id'],
435
                            $model->id,
436
                            $param['filename'],
437
                            $param['image_title'],
438
                            $param['image_alt'],
439
                            $param['sort_order'],
440
                        ];
441
                    }
442
443
                    Yii::$app->db->createCommand()->batchInsert(
444
                        Image::tableName(),
445
                        [
446
                            'object_id',
447
                            'object_model_id',
448
                            'filename',
449
                            'image_title',
450
                            'image_alt',
451
                            'sort_order',
452
                        ],
453
                        $rows
454
                    )->execute();
455
456
                }
457
            }
458
        }
459
    }
460
461
    /**
462
     * Clone product action.
463
     * @param integer $id
464
     * @throws \yii\web\NotFoundHttpException
465
     */
466
    public function actionClone($id, $returnUrl = ['index'])
467
    {
468
        /** @var Product|HasProperties $model */
469
        $model = Product::findOne($id);
470
        if ($model === null) {
471
            throw new NotFoundHttpException;
472
        }
473
474
        /** @var Product|HasProperties $newModel */
475
        $newModel = new Product;
476
        $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...
477
        $time = time();
478
        $newModel->name .= ' (copy ' . date('Y-m-d h:i:s', $time) . ')';
479
        $newModel->slug .= '-copy-' . date('Ymdhis', $time);
480
        $newModel->id = null;
481 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...
482
            $newModel->slug = substr(uniqid() . "-" . $model->slug, 0, 80);
483
        }
484
        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...
485
            $object = Object::getForClass(get_class($newModel));
486
            // save categories
487
            $categoriesTableName = Object::getForClass(Product::className())->categories_table_name;
488
            $query = new Query();
489
            $params = $query->select(['category_id', 'sort_order'])->from($categoriesTableName)->where(
490
                ['object_model_id' => $model->id]
491
            )->all();
492
            if (!empty($params)) {
493
                $rows = [];
494
                foreach ($params as $param) {
495
                    $rows[] = [
496
                        $param['category_id'],
497
                        $newModel->id,
498
                        $param['sort_order'],
499
                    ];
500
                }
501
                Yii::$app->db->createCommand()->batchInsert(
502
                    $categoriesTableName,
503
                    [
504
                        'category_id',
505
                        'object_model_id',
506
                        'sort_order'
507
                    ],
508
                    $rows
509
                )->execute();
510
            }
511
512
            // save images bindings
513
            $params = $query->select(
514
                ['object_id', 'filename', 'image_title', 'image_alt', 'sort_order']
515
            )->from(Image::tableName())->where(
516
                [
517
                    'object_id' => $object->id,
518
                    'object_model_id' => $model->id
519
                ]
520
            )->all();
521 View Code Duplication
            if (!empty($params)) {
522
                $rows = [];
523
                foreach ($params as $param) {
524
                    $rows[] = [
525
                        $param['object_id'],
526
                        $newModel->id,
527
                        $param['filename'],
528
                        $param['image_title'],
529
                        $param['image_alt'],
530
                        $param['sort_order'],
531
                    ];
532
                }
533
                Yii::$app->db->createCommand()->batchInsert(
534
                    Image::tableName(),
535
                    [
536
                        'object_id',
537
                        'object_model_id',
538
                        'filename',
539
                        'image_title',
540
                        'image_alt',
541
                        'sort_order',
542
                    ],
543
                    $rows
544
                )->execute();
545
            }
546
            $newModelProps = [];
547 View Code Duplication
            foreach (array_keys($model->propertyGroups) as $key) {
548
                $opg = new ObjectPropertyGroup();
549
                $opg->attributes = [
550
                    'object_id' => $object->id,
551
                    'object_model_id' => $newModel->id,
552
                    'property_group_id' => $key,
553
                ];
554
                $opg->save();
555
                $props = Property::getForGroupId($key);
556
                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...
557
                    $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...
558
                    if ($propValues !== null) {
559
                        foreach ($propValues->values as $val) {
560
                            $valueToSave = ArrayHelper::getValue($val, 'psv_id', $val['value']);
561
                            $newModelProps[$prop->key][] = $valueToSave;
562
                        }
563
                    }
564
                }
565
            }
566
            $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...
567
                [
568
                    'Properties_Product_' . $newModel->id => $newModelProps,
569
                ]
570
            );
571
572
            // addons cloning
573
            foreach ($model->bindedAddons as $index => $addon) {
574
                $newModel->link(
0 ignored issues
show
Bug introduced by
The method link 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...
575
                    'bindedAddons',
576
                    $addon,
577
                    [
578
                        'sort_order'=>$index,
579
                        'appliance_object_id' => $object->id,
580
                    ]
581
                );
582
            }
583
584
            Yii::$app->session->setFlash('success', Yii::t('app', 'Product has been cloned successfully.'));
585
            $this->redirect(['edit', 'id' => $newModel->id, 'returnUrl' => $returnUrl]);
586
        }
587
    }
588
589
    /**
590
     * @param $id
591
     * @return \yii\web\Response
592
     * @throws NotFoundHttpException
593
     * @throws \Exception
594
     */
595
    public function actionDelete($id)
596
    {
597
        /** @var Product $model */
598
        if (null === $model = Product::findOne($id)) {
599
            throw new NotFoundHttpException;
600
        }
601
602
        if (Yii::$app->request->get('returnUrl') !== null) {
603
            $redirect = Yii::$app->request->get('returnUrl');
604
        } elseif ($model->parent_id == 0) {
605
            $redirect = Url::toRoute(['index']);
606
        } else {
607
            $redirect = Url::toRoute(['edit', 'id' => $model->parent_id]);
608
        }
609
610
        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...
611
            Yii::$app->session->setFlash('info', Yii::t('app', 'The object is placed in the cart'));
612
        } else {
613
            Yii::$app->session->setFlash('info', Yii::t('app', 'Object has been removed'));
614
        }
615
616
        return $this->redirect($redirect);
617
    }
618
619
    /**
620
     * @param $parent_id
621
     * @return \yii\web\Response
622
     * @throws \Exception
623
     */
624
    public function actionRemoveAll($parent_id)
625
    {
626
        $items = Yii::$app->request->post('items', []);
627
        if (!empty($items)) {
628
            $items = Product::find()->where(['in', 'id', $items])->all();
629
            foreach ($items as $item) {
630
                $item->delete();
631
            }
632
        }
633
634
        return $this->redirect(['index', 'parent_id' => $parent_id]);
635
    }
636
637
    /**
638
     * @param $tableName
639
     * @param $ids
640
     * @param string $field
641
     * @return bool
642
     * @throws \yii\db\Exception
643
     */
644 View Code Duplication
    public static function sortModels($tableName, $ids, $field = 'sort_order')
645
    {
646
        $priorities = [];
647
        $start = 0;
648
        $ids_sorted = $ids;
649
        sort($ids_sorted);
650
        foreach ($ids as $id) {
651
            $priorities[$id] = $ids_sorted[$start++];
652
        }
653
        $sql = "UPDATE " . $tableName . " SET $field = " . self::generateCase($priorities) . " WHERE id IN(" . implode(
654
                ', ',
655
                $ids
656
            ) . ")";
657
658
        return Yii::$app->db->createCommand(
659
            $sql
660
        )->execute() > 0;
661
662
    }
663
664
    /**
665
     * Рекурсивный генератор свойств для создания комплектаций.
666
     * @param array $array Трехмерный массив вида:
667
     * [[['{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...
668
     * @param array $result Используется для передачи результатов внутри рекурсии
669
     * @param integer $count Счетчик внутри рекурсии
670
     * @return array
671
     */
672
    public static function generateOptions($array, $result = [], $count = 0)
673
    {
674
        if (empty($result)) {
675
            foreach ($array[$count] as $value) {
676
                $result[] = [$value];
677
            }
678
            $count++;
679
            $arResult = self::generateOptions($array, $result, $count);
680
        } else {
681
            if (isset($array[$count])) {
682
                $nextResult = [];
683
                foreach ($array[$count] as $value) {
684
                    foreach ($result as $resValue) {
685
                        $nextResult[] = array_merge($resValue, [$value]);
686
                    }
687
                }
688
                $count++;
689
                $arResult = self::generateOptions($array, $nextResult, $count);
690
            } else {
691
                return $result;
692
            }
693
        }
694
        return $arResult;
695
    }
696
697
    /**
698
     * @param $priorities
699
     * @return string
700
     */
701 View Code Duplication
    private static function generateCase($priorities)
702
    {
703
        $result = 'CASE `id`';
704
        foreach ($priorities as $k => $v) {
705
            $result .= ' when "' . $k . '" then "' . $v . '"';
706
        }
707
        return $result . ' END';
708
    }
709
710
    /**
711
     * @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...
712
     */
713
    public function actionAjaxRelatedProduct()
714
    {
715
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
716
717
        $result = [
718
            'more' => false,
719
            'results' => []
720
        ];
721
        $search = Yii::$app->request->get('search');
722
        if (!empty($search['term'])) {
723
            $query = new \yii\db\Query();
724
            $query->select('id, name AS text')->from(Product::tableName())->andWhere(
725
                ['like', 'name', $search['term']]
726
            )->andWhere(['active' => 1])->orderBy(['sort_order' => SORT_ASC, 'name' => SORT_ASC]);
727
            $command = $query->createCommand();
728
            $data = $command->queryAll();
729
730
            $result['results'] = array_values($data);
731
        }
732
733
        return $result;
734
    }
735
}