GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( f1313a...518c85 )
by Ivan
15:51
created

Product   F

Complexity

Total Complexity 144

Size/Duplication

Total Lines 1106
Duplicated Lines 7.05 %

Coupling/Cohesion

Components 1
Dependencies 32

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 144
c 3
b 0
f 0
lcom 1
cbo 32
dl 78
loc 1106
rs 0.5217

35 Methods

Rating   Name   Duplication   Size   Complexity  
A tableName() 0 4 1
A rules() 0 51 1
B attributeLabels() 0 32 1
B behaviors() 32 32 1
B search() 0 24 2
C findBySlug() 0 45 7
A getCategory() 0 4 1
A getOptions() 0 4 1
A getCurrency() 0 4 1
A getRelatedProducts() 0 15 1
A getImage() 0 6 1
B getWarehousesState() 0 24 2
B beforeSave() 0 24 4
B findById() 0 32 6
A afterSave() 0 7 1
A beforeDelete() 11 11 3
B saveCategoriesBindings() 7 40 5
A getCategoryIds() 0 11 2
D processImportBeforeSave() 0 34 11
D processImportAfterSave() 7 48 11
F unpackCategories() 21 78 24
B exportableAdditionalFields() 0 26 1
D getAdditionalFields() 0 41 10
F filteredProducts() 0 169 18
A getMainCategory() 0 4 1
A loadRelatedProductsArray() 0 7 2
A saveRelatedProducts() 0 22 3
A convertedPrice() 0 14 4
B formattedPrice() 0 31 3
A nativeCurrencyPrice() 0 5 1
A getMeasure() 0 11 3
C linkToCategory() 0 35 7
A getCacheTags() 0 16 2
A __toString() 0 4 1
A jsonSerialize() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

Complex classes like Product 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 Product, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace app\modules\shop\models;
4
5
use app\behaviors\CleanRelations;
6
use app\behaviors\Tree;
7
use app\components\Helper;
8
use app\modules\image\models\Image;
9
use app\models\Object;
10
use app\modules\data\components\ImportableInterface;
11
use app\modules\data\components\ExportableInterface;
12
use app\modules\shop\traits\HasAddonTrait;
13
use app\modules\shop\ShopModule;
14
use app\properties\HasProperties;
15
use app\traits\GetImages;
16
use devgroup\TagDependencyHelper\ActiveRecordHelper;
17
use Yii;
18
use yii\base\Exception;
19
use yii\behaviors\TimestampBehavior;
20
use yii\caching\TagDependency;
21
use yii\data\ActiveDataProvider;
22
use yii\db\ActiveQuery;
23
use yii\db\ActiveRecord;
24
use yii\db\Expression;
25
use yii\db\Query;
26
use yii\helpers\ArrayHelper;
27
use yii\data\Pagination;
28
29
/**
30
 * This is the model class for table "product".
31
 *
32
 * @property integer $id
33
 * @property integer $main_category_id
34
 * @property string $name
35
 * @property string $title
36
 * @property string $h1
37
 * @property string $meta_description
38
 * @property string $breadcrumbs_label
39
 * @property string $slug
40
 * @property string $slug_compiled
41
 * @property integer $slug_absolute
42
 * @property string $content
43
 * @property string $announce
44
 * @property integer $sort_order
45
 * @property integer $active
46
 * @property double $price
47
 * @property double $old_price
48
 * @property integer $parent_id
49
 * @property integer $currency_id
50
 * @property integer $measure_id
51
 * @property Product[] $relatedProducts
52
 * @property Measure $measure
53
 * @property string $sku
54
 * @property boolean unlimited_count
55
 * @property string $date_added
56
 * @property string $date_modified
57
 * Relations:
58
 * @property Category $category
59
 */
60
class Product extends ActiveRecord implements ImportableInterface, ExportableInterface, \JsonSerializable
61
{
62
    use GetImages;
63
    use HasAddonTrait;
64
65
    private static $identity_map = [];
66
    private static $slug_to_id = [];
67
    private $category_ids = null;
68
69
    public $relatedProductsArray = [];
70
71
    /**
72
     * @var null|WarehouseProduct[] Stores warehouses state of product. Use Product::getWarehousesState() to retrieve
73
     */
74
    private $activeWarehousesState = null;
75
76
    /**
77
     * @inheritdoc
78
     */
79
    public static function tableName()
80
    {
81
        return '{{%product}}';
82
    }
83
84
    /**
85
     * @inheritdoc
86
     */
87
    public function rules()
88
    {
89
        return [
90
            [['sku'], 'filter', 'filter' => ['\app\backend\components\Helper', 'toString']],
91
            [['main_category_id', 'name', 'slug'], 'required'],
92
            [
93
                [
94
                    'main_category_id',
95
                    'slug_absolute',
96
                    'sort_order',
97
                    'parent_id',
98
                    'currency_id',
99
                ],
100
                'integer'
101
            ],
102
            [
103
                [
104
                    'name',
105
                    'title',
106
                    'h1',
107
                    'meta_description',
108
                    'breadcrumbs_label',
109
                    'content',
110
                    'announce',
111
                    'option_generate',
112
                    'sku'
113
                ],
114
                'string'
115
            ],
116
            [
117
                [
118
                    'unlimited_count',
119
                    'active',
120
                    'slug_absolute',
121
                ],
122
                'boolean',
123
            ],
124
            [['price', 'old_price'], 'number'],
125
            [['slug'], 'string', 'max' => 80],
126
            [['slug_compiled'], 'string', 'max' => 180],
127
            [['old_price', 'price',], 'default', 'value' => 0,],
128
            [['active', 'unlimited_count'], 'default', 'value' => true],
129
            [['parent_id', 'slug_absolute', 'sort_order'], 'default', 'value' => 0],
130
            [['sku', 'name'], 'default', 'value' => ''],
131
            [['unlimited_count', 'currency_id', 'measure_id'], 'default', 'value' => 1],
132
            [['relatedProductsArray'], 'safe'],
133
            [['slug'], 'unique', 'targetAttribute' => ['slug', 'main_category_id']],
134
            [['date_added', 'date_modified'], 'safe'],
135
136
        ];
137
    }
138
    /**
139
     * @inheritdoc
140
     */
141
    public function attributeLabels()
142
    {
143
        return [
144
            'id' => Yii::t('app', 'ID'),
145
            'main_category_id' => Yii::t('app', 'Main Category ID'),
146
            'parent_id' => Yii::t('app', 'Parent ID'),
147
            'name' => Yii::t('app', 'Name'),
148
            'title' => Yii::t('app', 'Title'),
149
            'h1' => Yii::t('app', 'H1'),
150
            'meta_description' => Yii::t('app', 'Meta Description'),
151
            'breadcrumbs_label' => Yii::t('app', 'Breadcrumbs Label'),
152
            'slug' => Yii::t('app', 'Slug'),
153
            'slug_compiled' => Yii::t('app', 'Slug Compiled'),
154
            'slug_absolute' => Yii::t('app', 'Slug Absolute'),
155
            'content' => Yii::t('app', 'Content'),
156
            'announce' => Yii::t('app', 'Announce'),
157
            'sort_order' => Yii::t('app', 'Sort Order'),
158
            'active' => Yii::t('app', 'Active'),
159
            'price' => Yii::t('app', 'Price'),
160
            'old_price' => Yii::t('app', 'Old Price'),
161
            'option_generate' => Yii::t('app', 'Option Generate'),
162
            'in_warehouse' => Yii::t('app', 'Items in warehouse'),
163
            'sku' => Yii::t('app', 'SKU'),
164
            'unlimited_count' => Yii::t('app', 'Unlimited items(don\'t count in warehouse)'),
165
            'reserved_count' => Yii::t('app', 'Items reserved'),
166
            'relatedProductsArray' => Yii::t('app', 'Related products'),
167
            'currency_id' => Yii::t('app', 'Currency'),
168
            'measure_id' => Yii::t('app', 'Measure'),
169
            'date_added' => Yii::t('app', 'Date Added'),
170
            'date_modified' => Yii::t('app', 'Date Modified'),
171
        ];
172
    }
173
174
    /**
175
     * @inheritdoc
176
     */
177 View Code Duplication
    public function behaviors()
178
    {
179
        return [
180
            [
181
                'class' => Tree::className(),
182
                'activeAttribute' => 'active',
183
                'sortOrder' => [
184
                    'sort_order' => SORT_ASC,
185
                    'id' => SORT_ASC
186
                ],
187
            ],
188
            [
189
                'class' => HasProperties::className(),
190
            ],
191
            [
192
                'class' => \devgroup\TagDependencyHelper\ActiveRecordHelper::className(),
193
            ],
194
            [
195
                'class' => CleanRelations::className(),
196
            ],
197
            [
198
                'class' => TimestampBehavior::className(),
199
                'createdAtAttribute' => 'date_added',
200
                'updatedAtAttribute' => 'date_modified',
201
                'value' => new Expression('NOW()'),
202
                'attributes' => [
203
                    ActiveRecord::EVENT_BEFORE_INSERT => ['date_added'],
204
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['date_modified'],
205
                ],
206
            ],
207
        ];
208
    }
209
210
    /**
211
     * Search products
212
     * @param $params
213
     * @return ActiveDataProvider
214
     */
215
    public function search($params)
216
    {
217
        /* @var $query \yii\db\ActiveQuery */
218
        $query = self::find()->where(['parent_id' => 0])->with('images');
219
        $dataProvider = new ActiveDataProvider(
220
            [
221
                'query' => $query,
222
                'pagination' => [
223
                    'pageSize' => 10,
224
                ],
225
            ]
226
        );
227
        if (!($this->load($params))) {
228
            return $dataProvider;
229
        }
230
        $query->andFilterWhere(['id' => $this->id]);
231
        $query->andFilterWhere(['like', 'name', $this->name]);
232
        $query->andFilterWhere(['like', 'slug', $this->slug]);
233
        $query->andFilterWhere(['active' => $this->active]);
234
        $query->andFilterWhere(['price' => $this->price]);
235
        $query->andFilterWhere(['old_price' => $this->old_price]);
236
        $query->andFilterWhere(['like', 'sku', $this->sku]);
237
        return $dataProvider;
238
    }
239
240
    /**
241
     * Returns model instance by ID using per-request Identity Map and cache
242
     * @param $id
243
     * @param int $is_active Return only active
0 ignored issues
show
Bug introduced by
There is no parameter named $is_active. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
244
     * @return Product
245
     */
246
    public static function findById($id, $isActive = 1)
247
    {
248
        if (!is_numeric($id)) {
249
            return null;
250
        }
251
        if (!isset(static::$identity_map[$id])) {
252
            $cacheKey = static::tableName() . ":$id:$isActive";
253
            if (false === $model = Yii::$app->cache->get($cacheKey)) {
254
                $model = static::find()->where(['id' => $id])->with('images');
255
                if (null !== $isActive) {
256
                    $model->andWhere(['active' => $isActive]);
257
                }
258
                if (null !== $model = $model->one()) {
259
                    static::$slug_to_id[$model->slug] = $id;
260
                    Yii::$app->cache->set(
261
                        $cacheKey,
262
                        $model,
263
                        86400,
264
                        new TagDependency(
265
                            [
266
                                'tags' => [
267
                                    \devgroup\TagDependencyHelper\ActiveRecordHelper::getCommonTag(static::className())
268
                                ]
269
                            ]
270
                        )
271
                    );
272
                }
273
            }
274
            static::$identity_map[$id] = $model;
275
        }
276
        return static::$identity_map[$id];
277
    }
278
279
    /**
280
     * Find a product by slug
281
     * @param string $slug
282
     * @param int $inCategoryId
0 ignored issues
show
Documentation introduced by
Should the type for parameter $inCategoryId not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
283
     * @param int $isActive
284
     * @return Product
285
     */
286
    public static function findBySlug($slug, $inCategoryId = null, $isActive = 1)
287
    {
288
        if (!isset(static::$slug_to_id[$slug])) {
289
            $cacheKey = static::tableName() . "$slug:$inCategoryId";
290
            if (false === $model = Yii::$app->cache->get($cacheKey)) {
291
                $tags = [];
292
                /** @var ActiveQuery $model */
293
                $query = static::find()->where(
294
                    [
295
                        'slug' => $slug,
296
                        'active' => $isActive,
297
                    ]
298
                )->with('images', 'relatedProducts');
299
                if (!is_null($inCategoryId)) {
300
                    $query->andWhere(['main_category_id' => $inCategoryId]);
301
                    $tags[] = ActiveRecordHelper::getObjectTag(Category::className(), $inCategoryId);
302
                }
303
                $model = $query->one();
304
                if (is_null($model)) {
305
                    $tags[] = ActiveRecordHelper::getCommonTag(Product::className());
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
306
                } else {
307
                    $tags[] = ActiveRecordHelper::getObjectTag(Product::className(), $model->id);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
308
                }
309
                Yii::$app->cache->set(
310
                    $cacheKey,
311
                    $model,
312
                    86400,
313
                    new TagDependency([
314
                        'tags' => $tags,
315
                    ])
316
                );
317
            }
318
            if (is_object($model)) {
319
                static::$identity_map[$model->id] = $model;
320
                static::$slug_to_id[$slug] = $model->id;
321
                return $model;
322
            }
323
            return null;
324
        } else {
325
            if (isset(static::$identity_map[static::$slug_to_id[$slug]])) {
326
                return static::$identity_map[static::$slug_to_id[$slug]];
327
            }
328
            return static::findById(static::$slug_to_id[$slug]);
329
        }
330
    }
331
332
    /**
333
     * @return Category|null
0 ignored issues
show
Documentation introduced by
Should the return type not be ActiveQuery?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
334
     */
335
    public function getCategory()
336
    {
337
        return $this->hasOne(Category::className(), ['id' => 'main_category_id']);
338
    }
339
340
    public function getOptions()
341
    {
342
        return $this->hasMany(static::className(), ['parent_id' => 'id']);
343
    }
344
345
    public function getCurrency()
346
    {
347
        return $this->hasOne(Currency::className(), ['id' => 'currency_id']);
348
    }
349
350
    /**
351
     * @return ActiveQuery
352
     */
353
    public function getRelatedProducts()
354
    {
355
        return $this->hasMany(Product::className(), ['id' => 'related_product_id'])
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
356
            ->viaTable(
357
                RelatedProduct::tableName(),
358
                ['product_id' => 'id'],
359
                function($relation) {
360
                    /** @var \yii\db\ActiveQuery $relation */
361
                    return $relation->orderBy('sort_order ASC');
362
                }
363
            )
364
            ->innerJoin('{{%related_product}}','related_product.product_id=:id AND related_product.related_product_id =product.id',[':id'=>$this->id])
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 150 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...
365
            ->orderBy('related_product.sort_order ASC')
366
            ->andWhere(["active" => 1]);
367
    }
368
369
    public function getImage()
370
    {
371
        $result = $this->hasOne(Image::className(), ['object_model_id' => 'id']);
372
        $object = Object::getForClass($this->className());
373
        return $result->andWhere(['object_id' => $object->id]);
374
    }
375
376
377
    /**
378
     * Returns remains of this product in all active warehouses.
379
     * Note that if warehouse was added after product edit - it will not be shown here.
380
     * @return WarehouseProduct[]
381
     */
382
    public function getWarehousesState()
383
    {
384
        if ($this->activeWarehousesState === null) {
385
            $this->activeWarehousesState = WarehouseProduct::getDb()->cache(
386
                function($db) {
0 ignored issues
show
Unused Code introduced by
The parameter $db 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...
387
                    return WarehouseProduct::find()
388
                        ->where(['in', 'warehouse_id', Warehouse::activeWarehousesIds()])
389
                        ->andWhere('product_id=:product_id', [':product_id'=>$this->id])
390
                        ->with('warehouse')
391
                        ->all();
392
                },
393
                86400,
394
                new TagDependency(
395
                    [
396
                        'tags' => [
397
                            ActiveRecordHelper::getObjectTag($this->className(), $this->id),
398
                        ]
399
                    ]
400
                )
401
            );
402
        }
403
404
        return $this->activeWarehousesState;
405
    }
406
407
    public function beforeSave($insert)
408
    {
409
        if (empty($this->breadcrumbs_label)) {
410
            $this->breadcrumbs_label = $this->name;
411
        }
412
413
        if (empty($this->h1)) {
414
            $this->h1 = $this->name;
415
        }
416
417
        if (empty($this->title)) {
418
            $this->title = $this->name;
419
        }
420
        $object = Object::getForClass(static::className());
421
422
        \yii\caching\TagDependency::invalidate(
423
            Yii::$app->cache,
424
            [
425
                'Images:' . $object->id . ':' . $this->id
426
            ]
427
        );
428
429
        return parent::beforeSave($insert);
430
    }
431
432
    /**
433
     * @inheritdoc
434
     */
435
    public function afterSave($insert, $changedAttributes)
436
    {
437
        parent::afterSave($insert, $changedAttributes);
438
439
        static::$identity_map[$this->id] = $this;
440
441
    }
442
443
    /**
444
     * Preparation to delete product.
445
     * Deleting all inserted products.
446
     * @return bool
447
     */
448 View Code Duplication
    public function beforeDelete()
449
    {
450
        if (!parent::beforeDelete()) {
451
            return false;
452
        }
453
        foreach ($this->children as $child) {
0 ignored issues
show
Documentation introduced by
The property children 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...
454
            /** @var Product $child */
455
            $child->delete();
456
        }
457
        return true;
458
    }
459
460
    public function saveCategoriesBindings(array $categories_ids)
461
    {
462
        $object = Object::getForClass(static::className());
463
        $catIds = $this->getCategoryIds();
464
465
        $remove = [];
466
        $add = [];
467
468
        foreach ($catIds as $catsKey => $value) {
469
            $key = array_search($value, $categories_ids);
470
            if ($key === false) {
471
                $remove[] = $value;
472
                unset($this->category_ids[$catsKey]);
473
            } else {
474
                unset($categories_ids[$key]);
475
            }
476
        }
477
        foreach ($categories_ids as $value) {
478
            $add[] = [
479
                $value,
480
                $this->id
481
            ];
482
            $this->category_ids[] = $value;
483
        }
484
485
        Yii::$app->db->createCommand()->delete(
486
            $object->categories_table_name,
487
            ['and', 'object_model_id = :id', ['in', 'category_id', $remove]],
488
            [':id' => $this->id]
489
        )->execute();
490 View Code Duplication
        if (!empty($add)) {
491
            Yii::$app->db->createCommand()->batchInsert(
492
                $object->categories_table_name,
493
                ['category_id', 'object_model_id'],
494
                $add
495
            )->execute();
496
        }
497
498
499
    }
500
501
    /**
502
     * @return array
503
     */
504
    public function getCategoryIds()
505
    {
506
        if ($this->category_ids === null) {
507
            $object = Object::getForClass(static::className());
508
            $this->category_ids = (new Query())->select('category_id')->from([$object->categories_table_name])->where(
509
                'object_model_id = :id',
510
                [':id' => $this->id]
511
            )->orderBy(['sort_order' => SORT_ASC, 'id' => SORT_ASC])->column();
512
        }
513
        return $this->category_ids;
514
    }
515
516
    /**
517
     * Process fields before the actual model is saved(inserted or updated)
518
     * @param array $fields
519
     * @return void
520
     */
521
    public function processImportBeforeSave(array $fields, $multipleValuesDelimiter, array $additionalFields)
522
    {
523
        $_attributes = $this->attributes();
524
        foreach ($fields as $key => $value) {
525
            if (in_array($key, $_attributes)) {
526
                $this->$key = $value;
527
            }
528
        }
529
530
        $categories = $this->unpackCategories($fields, $multipleValuesDelimiter, $additionalFields);
531
        if ($categories !== false && $this->main_category_id < 1) {
532
            if (count($categories) == 0) {
533
                $categories = [1];
534
            }
535
            $this->main_category_id = $categories[0];
536
        }
537
538
        if (empty($this->slug)) {
539
            $this->slug = Helper::createSlug($this->name);
540
        } elseif (mb_strlen($this->slug) > 80) {
541
            $this->slug = mb_substr($this->slug, 0, 80);
542
        }
543
544
        if (empty($this->name)) {
545
            $this->name = 'unnamed-product';
546
        }
547
548
        if (!is_numeric($this->price)) {
549
            $this->price = 0;
550
        }
551
        if (!is_numeric($this->old_price)) {
552
            $this->old_price = 0;
553
        }
554
    }
555
556
    /**
557
     * Process fields after the actual model is saved(inserted or updated)
558
     * @param array $fields
559
     * @return void
560
     */
561
    public function processImportAfterSave(array $fields, $multipleValuesDelimiter, array $additionalFields)
562
    {
563
        $categories = $this->unpackCategories($fields, $multipleValuesDelimiter, $additionalFields);
564
565
        if ($categories === false) {
566
            $categories = [$this->main_category_id];
567
        }
568
        $this->saveCategoriesBindings($categories);
0 ignored issues
show
Bug introduced by
It seems like $categories defined by $this->unpackCategories(...ter, $additionalFields) on line 563 can also be of type boolean; however, app\modules\shop\models\...aveCategoriesBindings() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
569
570
571
        $images = isset($fields['images']) ? $fields['images'] : (isset($fields['image']) ? $fields['image'] : false);
572
        if ($images !== false) {
573 View Code Duplication
            if (strpos($images, $multipleValuesDelimiter) > 0) {
574
                $images = explode($multipleValuesDelimiter, $images);
575
            } elseif (strpos($multipleValuesDelimiter, '/') === 0) {
576
                $images = preg_split($multipleValuesDelimiter, $images);
577
            } else {
578
                $images = [$images];
579
            }
580
            $input_array = [];
581
            foreach ($images as $image_src) {
582
                $input_array[] = [
583
                    'filename' => $image_src,
584
                ];
585
            }
586
            if (count($input_array) > 0) {
587
                Image::replaceForModel($this, $input_array);
588
            }
589
        }
590
591
        if (isset($additionalFields['relatedProducts'])) {
592
            if (isset($additionalFields['relatedProducts']['processValuesAs'],$fields['relatedProducts'])) {
593
594
                $ids = explode($multipleValuesDelimiter, $fields['relatedProducts']);
595
                $this->relatedProductsArray=$ids;
596
                $this->saveRelatedProducts();
597
//                $this->unlinkAll('relatedProducts', true);
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
598
//
599
//                foreach ($ids as $index => $id) {
600
//                    $product = Product::findById($id);
601
//                    $this->link('relatedProduct', $product, ['sort_order'=>$index]);
602
//                }
603
604
605
606
            }
607
        }
608
    }
609
610
    /**
611
     * Makes an array of category ids from string
612
     *
613
     * @param array $fields
614
     * @param $multipleValuesDelimiter
615
     * @param array $additionalFields
616
     * @return array|bool
617
     */
618
    private function unpackCategories(array $fields, $multipleValuesDelimiter, array $additionalFields)
619
    {
620
        $categories = isset($fields['categories']) ? $fields['categories'] : (isset($fields['category']) ? $fields['category'] : false);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 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...
621
        if ($categories === false || empty($fields['categories'])) {
622
            return $this->getCategoryIds();
623
624
        }
625
        if ($categories !== false) {
626 View Code Duplication
            if (strpos($categories, $multipleValuesDelimiter) > 0) {
627
                $categories = explode($multipleValuesDelimiter, $categories);
628
            } elseif (strpos($multipleValuesDelimiter, '/') === 0) {
629
                $categories = preg_split($multipleValuesDelimiter, $categories);
630
            } else {
631
                $categories = [$categories];
632
            }
633
            $typecast = 'id';
634
635
            if (isset($additionalFields['categories'])) {
636
                if (isset($additionalFields['categories']['processValuesAs'])) {
637
                    $typecast = $additionalFields['categories']['processValuesAs'];
638
                }
639
            }
640
            if ($typecast === 'id') {
641
                $categories = array_map('intval', $categories);
642
            } elseif ($typecast === 'slug') {
643
                $categories = array_map('trim', $categories);
644
                $categoryIds = [];
645 View Code Duplication
                foreach ($categories as $part) {
646
                    $cat = Category::findBySlug($part, 1, -1);
647
                    if (is_object($cat)) {
648
                        $categoryIds[] = $cat->id;
649
                    }
650
                    unset($cat);
651
                }
652
                $categories = array_map('intval', $categoryIds);
653
            } elseif ($typecast === 'name') {
654
                $categories = array_map('trim', $categories);
655
                $categoryIds = [];
656 View Code Duplication
                foreach ($categories as $part) {
657
                    $cat = Category::findByName($part, 1, -1);
658
                    if (is_object($cat)) {
659
                        $categoryIds[] = $cat->id;
660
                    }
661
                    unset($cat);
662
                }
663
                $categories = array_map('intval', $categoryIds);
664
            } else {
665
                // that's unusual behavior
666
                $categories = false;
667
            }
668
669
            // post-process categories
670
            // find & add parent category
671
            // if we need to show products of child categories in products list
672
            /** @var ShopModule $module */
673
            $module = Yii::$app->getModule('shop');
674
            if (is_array($categories) && $module->showProductsOfChildCategories) {
675
                do {
676
                    $repeat = false;
677
                    foreach ($categories as $cat) {
678
                        $model = Category::findById($cat, null, null);
0 ignored issues
show
Unused Code introduced by
The call to Category::findById() has too many arguments starting with null.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
679
                        if ($model === null) {
680
                            echo "\n\nUnknown category with id ".intval($cat) ." for model with id:".$this->id."\n\n";
681
                            continue;
682
                        }
683
                        if (intval($model->parent_id) > 0 && in_array($model->parent_id, $categories) === false) {
684
                            $categories[] = $model->parent_id;
685
                            $repeat = true;
686
                        }
687
688
                        unset($model);
689
                    }
690
                } while ($repeat === true);
691
            }
692
693
        }
694
        return $categories;
695
    }
696
697
    /**
698
     * Additional fields with labels.
699
     * Translation should be implemented internally in this function.
700
     * For now will be rendered as checkbox list with label.
701
     * Note: properties should not be in the result - they are served other way.
702
     * Format:
703
     * [
704
     *      'field_key' => 'Your awesome translated field title',
705
     *      'another' => 'Another field label',
706
     * ]
707
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array<strin...|array<string,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...
708
     */
709
    public static function exportableAdditionalFields()
710
    {
711
        return [
712
            'categories' => [
713
                'label' => Yii::t('app', 'Categories'),
714
                'processValueAs' => [
715
                    'id' => Yii::t('app', 'ID'),
716
                    'name' => Yii::t('app', 'Name'),
717
                    'slug' => Yii::t('app', 'Slug'),
718
                ]
719
            ],
720
            'images' => [
721
                'label' => Yii::t('app', 'Images'),
722
                'processValueAs' => [
723
                    'filename' => Yii::t('app', 'Filename'),
724
                    'id' => Yii::t('app', 'ID'),
725
                ]
726
            ],
727
            'relatedProducts' => [
728
                'label' => Yii::t('app', 'Related products'),
729
                'processValueAs' => [
730
                    'id' => Yii::t('app', 'ID'),
731
                ],
732
            ],
733
        ];
734
    }
735
736
    /**
737
     * Returns additional fields data by field key.
738
     * If value of field is array it will be converted to string
739
     * using multipleValuesDelimiter specified in ImportModel
740
     * @return array
741
     */
742
    public function getAdditionalFields(array $configuration)
743
    {
744
        $result = [];
745
746
        if (isset($configuration['categories'], $configuration['categories']['processValuesAs'])
747
            && $configuration['categories']['enabled']
748
        ) {
749
            if ($configuration['categories']['processValuesAs'] === 'id') {
750
                $result['categories'] = $this->getCategoryIds();
751
            } else {
752
                $ids = $this->getCategoryIds();
753
                $result['categories'] = [];
754
755
                foreach ($ids as $id) {
756
                    $category = Category::findById($id, null, null);
0 ignored issues
show
Unused Code introduced by
The call to Category::findById() has too many arguments starting with null.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
757
                    if ($category) {
758
                        $result['categories'][] = $category->getAttribute(
759
                            $configuration['categories']['processValuesAs']
760
                        );
761
                    }
762
                    unset($category);
763
                }
764
            }
765
        }
766
        if (isset($configuration['images'], $configuration['images']['processValuesAs'])
767
            && $configuration['images']['enabled']
768
        ) {
769
            $object = Object::getForClass($this->className());
770
            $images = Image::getForModel($object->id, $this->id);
771
            $result['images'] = ArrayHelper::getColumn($images, $configuration['images']['processValuesAs']);
772
        }
773
774
        if (isset($configuration['relatedProducts'], $configuration['relatedProducts']['processValuesAs'])
775
            && $configuration['relatedProducts']['enabled']
776
        ) {
777
            $result['relatedProducts'] = ArrayHelper::getColumn($this->relatedProducts, $configuration['relatedProducts']['processValuesAs']);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 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...
778
        }
779
780
781
        return $result;
782
    }
783
784
    /**
785
     * Returns products for special filtration query
786
     * Used in ProductsWidget and ProductController
787
     *
788
     * @param $category_group_id
789
     * @param array $values_by_property_id
790
     * @param null|integer|string $selected_category_id
791
     * @param bool|string $force_sorting If false - use UserPreferences, if string - use supplied orderBy condition
792
     * @param null|integer $limit limit query results
793
     * @param bool $apply_filterquery Should we apply filter query(filters based on query params ie. price_min/max)
794
     * @param bool $force_limit False to use Pagination, true to use $limit and ignore pagination
795
     * @param array $additional_filters Array of callables that will apply additional filters to query
796
     */
797
    public static function filteredProducts(
798
        $category_group_id,
799
        array $values_by_property_id = [],
800
        $selected_category_id = null,
801
        $force_sorting = false,
802
        $limit = null,
803
        $apply_filterquery = true,
804
        $force_limit = false,
805
        array $additional_filters = []
806
    ) {
807
        Yii::beginProfile("FilteredProducts");
808
        if (null === $object = Object::getForClass(static::className())) {
809
            throw new \yii\web\ServerErrorHttpException('Object not found.');
810
        }
811
812
        /** @var \app\modules\shop\ShopModule $module */
813
        $module = Yii::$app->getModule('shop');
814
815
        $onlyParents = $module->filterOnlyByParentProduct;
816
        $query = static::find()->with('images');
817
        if (true === $onlyParents) {
818
            $query->andWhere([static::tableName() . '.parent_id' => 0, static::tableName() . '.active' => 1]);
819
        } else {
820
            $query->andWhere(['!=', static::tableName() . '.parent_id', 0]);
821
            $query->andWhere([static::tableName() . '.active' => 1]);
822
        }
823
824
        if (null !== $selected_category_id) {
825
            $query->innerJoin(
826
                $object->categories_table_name . ' ocats',
827
                'ocats.category_id = :catid AND ocats.object_model_id = ' . static::tableName() . '.id',
828
                [':catid' => $selected_category_id]
829
            );
830
        } else {
831
            $query->innerJoin(
832
                $object->categories_table_name . ' ocats',
833
                'ocats.object_model_id = ' . static::tableName() . '.id'
834
            );
835
        }
836
837
        $query->innerJoin(
838
            Category::tableName() . ' ocatt',
839
            'ocatt.id = ocats.category_id AND ocatt.category_group_id = :gcatid AND ocatt.active = 1',
840
            [':gcatid' => $category_group_id]
841
        );
842
        $query->addGroupBy(static::tableName() . ".id");
843
844
845
        $userSelectedSortingId = UserPreferences::preferences()->getAttributes()['productListingSortId'];
846
        $allSorts = [];
847
        if ($force_sorting === false) {
848
            $allSorts = ProductListingSort::enabledSorts();
849
            if (isset($allSorts[$userSelectedSortingId])) {
850
                $query->addOrderBy(
851
                    $allSorts[$userSelectedSortingId]['sort_field'] . ' '
852
                    . $allSorts[$userSelectedSortingId]['asc_desc']
853
                );
854
            } else {
855
                $query->addOrderBy(static::tableName() . '.sort_order');
856
            }
857
        } elseif (empty($force_sorting) === false || is_array($force_sorting) === true) {
858
            $query->addOrderBy($force_sorting);
859
        }
860
861
        $productsPerPage = $limit === null ? UserPreferences::preferences()->getAttributes(
862
        )['productsPerPage'] : $limit;
863
864
        \app\properties\PropertiesHelper::appendPropertiesFilters(
865
            $object,
866
            $query,
867
            $values_by_property_id,
868
            Yii::$app->request->get('p', [])
869
        );
870
871
872
        // apply additional filters
873
        $cacheKeyAppend = "";
874
        if ($apply_filterquery) {
875
            $query = Yii::$app->filterquery->filter($query, $cacheKeyAppend);
876
        }
877
878
        foreach ($additional_filters as $filter) {
879
            if (is_callable($filter)) {
880
                call_user_func_array(
881
                    $filter,
882
                    [
883
                        &$query,
884
                        &$cacheKeyAppend
885
                    ]
886
                );
887
            }
888
        }
889
890
        $cacheKey = 'ProductsCount:' . implode(
891
                '_',
892
                [
893
                    md5($query->createCommand()->getRawSql()),
894
                    $limit ? '1' : '0',
895
                    $force_limit ? '1' : '0',
896
                    $productsPerPage
897
                ]
898
            ) . $cacheKeyAppend;
899
900
901
        $pages = null;
902
903
        if ($force_limit === true) {
904
            $query->limit($limit);
905
        } else {
906
            $products_query = clone $query;
907
            $products_query->limit(null);
908
909
            if (false === $pages = Yii::$app->cache->get($cacheKey)) {
910
                $pages = new Pagination(
911
                    [
912
                        'defaultPageSize' => !is_null($query->limit) ? $query->limit : $productsPerPage,
913
                        'pageSizeLimit' => [],
914
                        'forcePageParam' => false,
915
                        'totalCount' => $products_query->count(),
916
                    ]
917
                );
918
919
                Yii::$app->cache->set(
920
                    $cacheKey,
921
                    $pages,
922
                    86400,
923
                    new TagDependency(
924
                        [
925
                            'tags' => [
926
                                ActiveRecordHelper::getCommonTag(Category::className()),
927
                                ActiveRecordHelper::getCommonTag(static::className()),
928
                                ActiveRecordHelper::getCommonTag($module->className()),
929
                            ]
930
                        ]
931
                    )
932
                );
933
            }
934
935
            $query->offset($pages->offset)->limit($pages->limit);
936
        }
937
938
        $cacheKey .= '-' . Yii::$app->request->get('page', 1);
939
940
        if (false === $products = Yii::$app->cache->get($cacheKey)) {
941
            $products = $query->all();
942
            Yii::$app->cache->set(
943
                $cacheKey,
944
                $products,
945
                86400,
946
                new TagDependency(
947
                    [
948
                        'tags' => [
949
                            ActiveRecordHelper::getCommonTag(Category::className()),
950
                            ActiveRecordHelper::getCommonTag(static::className()),
951
                            ActiveRecordHelper::getCommonTag($module->className()),
952
                        ]
953
                    ]
954
                )
955
            );
956
        }
957
958
        Yii::endProfile("FilteredProducts");
959
        return [
960
            'products' => $products,
961
            'pages' => $pages,
962
            'allSorts' => $allSorts,
963
        ];
964
965
    }
966
967
    /**
968
     * Returns product main category model instance using per-request Identity Map
969
     * @return Category|null
970
     */
971
    public function getMainCategory()
972
    {
973
        return Category::findById($this->main_category_id, null);
974
    }
975
976
    public function loadRelatedProductsArray()
977
    {
978
        $this->relatedProductsArray = [];
979
        foreach ($this->relatedProducts as $product) {
980
            $this->relatedProductsArray[] = $product->id;
981
        }
982
    }
983
984
    public function saveRelatedProducts()
985
    {
986
        if (!is_array($this->relatedProductsArray)) {
987
            $this->relatedProductsArray = explode(',', $this->relatedProductsArray);
988
        }
989
990
        RelatedProduct::deleteAll(
991
            [
992
                'product_id' => $this->id,
993
            ]
994
        );
995
996
        foreach ($this->relatedProductsArray as $index => $relatedProductId) {
997
            $relation = new RelatedProduct;
998
            $relation->attributes = [
999
                'product_id' => $this->id,
1000
                'related_product_id' => $relatedProductId,
1001
                'sort_order' => $index,
1002
            ];
1003
            $relation->save();
1004
        }
1005
    }
1006
1007
    /**
1008
     * Returns product price(old old_price) converted to $currency. If currency is not set(null) will be converted to main currency
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 131 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...
1009
     * @param null|Currency $currency Currency to use in conversion, null for main shop currency
1010
     * @param bool $oldPrice True if you want to return Old Price instead of price
1011
     * @return float
1012
     */
1013
    public function convertedPrice($currency = null, $oldPrice = false)
1014
    {
1015
        if ($currency === null) {
1016
            $currency = Currency::getMainCurrency();
1017
        }
1018
        $price = $oldPrice === true ? $this->old_price : $this->price;
1019
1020
        if ($this->currency_id !== $currency->id) {
1021
            // we need to convert!
1022
            $foreignCurrency = Currency::findById($this->currency_id);
1023
            return round($price / $foreignCurrency->convert_nominal * $foreignCurrency->convert_rate, 2);
1024
        }
1025
        return $price;
1026
    }
1027
1028
    /**
1029
     * Formats price to needed currency
1030
     * @param null|Currency $currency Currency to use in conversion, null for main shop currency
1031
     * @param boolean $oldPrice True if you want to return Old Price instead of price
1032
     * @param boolean $schemaOrg Return schema.org http://schema.org/Offer markup with span's
1033
     * @return string
1034
     */
1035
    public function formattedPrice($currency = null, $oldPrice = false, $schemaOrg = true)
1036
    {
1037
        if ($currency === null) {
1038
            $currency = Currency::getMainCurrency();
1039
        }
1040
1041
        $price = $this->convertedPrice($currency, $oldPrice);
1042
1043
1044
        $formatted_string = $currency->format($price);
1045
        if ($schemaOrg == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
1046
            return strtr(
1047
                '
1048
                <span itemtype="http://schema.org/Offer" itemprop="offers" itemscope>
1049
                    <meta itemprop="priceCurrency" content="%iso_code%">
1050
                    <span itemprop="price" content="%price%">
1051
                        %formatted_string%
1052
                    </span>
1053
                </span>
1054
                ',
1055
                [
1056
                    '%iso_code%' => $currency->iso_code,
1057
                    '%price%' => $price,
1058
                    '%formatted_string%' => $formatted_string,
1059
                ]
1060
            );
1061
        } else {
1062
            return $formatted_string;
1063
        }
1064
1065
    }
1066
1067
    /**
1068
     * Formats price in product's currency
1069
     * @param bool $oldPrice
1070
     * @param bool $schemaOrg
1071
     * @return string
1072
     */
1073
    public function nativeCurrencyPrice($oldPrice = false, $schemaOrg = true)
1074
    {
1075
        $currency = Currency::findById($this->currency_id);
1076
        return $this->formattedPrice($currency, $oldPrice, $schemaOrg);
1077
    }
1078
1079
    public function getMeasure()
1080
    {
1081
        $measure = Measure::findById($this->measure_id);
1082
        if (is_null($measure)) {
1083
            $measure = Measure::findById(Yii::$app->getModule('shop')->defaultMeasureId);
1084
        }
1085
        if (is_null($measure)) {
1086
            throw new Exception('Measure not found');
1087
        }
1088
        return $measure;
1089
    }
1090
1091
    /**
1092
     * @param int|Category|null $category
1093
     * @param bool $asMainCategory
1094
     * @return bool
1095
     * @throws \yii\db\Exception
1096
     */
1097
    public function linkToCategory($category = null, $asMainCategory = false)
1098
    {
1099
        if ($category instanceof Category) {
1100
            $category = $category->id;
1101
        } elseif (is_int($category) || is_string($category)) {
1102
            $category = intval($category);
1103
        } else {
1104
            return false;
1105
        }
1106
1107
        if ($asMainCategory) {
1108
            $this->main_category_id = $category;
1109
            return $this->save();
1110
        } else {
1111
            $tableName = $this->getObject()->categories_table_name;
0 ignored issues
show
Documentation Bug introduced by
The method getObject 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...
1112
            $query = new Query();
1113
            $query = $query->select(['id'])
1114
                ->from($tableName)
1115
                ->where(['category_id' => $category, 'object_model_id' => $this->id])
1116
                ->one();
1117
            if (false === $query) {
1118
                try {
1119
                    $result = Yii::$app->db->createCommand()->insert($tableName, [
1120
                        'category_id' => $category,
1121
                        'object_model_id' => $this->id
1122
                    ])->execute();
1123
                } catch (\yii\db\Exception $e) {
1124
                    $result = false;
1125
                }
1126
                return boolval($result);
1127
            }
1128
        }
1129
1130
        return false;
1131
    }
1132
1133
    public function getCacheTags()
1134
    {
1135
1136
        $tags = [
1137
            ActiveRecordHelper::getObjectTag(self::className(), $this->id),
1138
        ];
1139
        $category = $this->getMainCategory();
1140
        $tags [] = ActiveRecordHelper::getObjectTag(Category::className(), $category->id);
1141
        $categoryParentsIds = $category->getParentIds();
1142
        foreach ($categoryParentsIds as $id) {
1143
            $tags [] = ActiveRecordHelper::getObjectTag(Category::className(), $id);
1144
        };
1145
1146
1147
        return $tags;
1148
    }
1149
1150
    /**
1151
     * @return string
1152
     */
1153
    public function __toString()
1154
    {
1155
        return ($this->className() . ':' . $this->id);
1156
    }
1157
1158
    /**
1159
     * @return string
1160
     */
1161
    public function jsonSerialize()
1162
    {
1163
        return ($this->className() . ':' . $this->id);
1164
    }
1165
}
1166
?>
1167