<commit_before><?php

namespace DotPlant\Store\models\goods;

use DevGroup\AdminUtils\traits\FetchModels;
use DevGroup\DataStructure\behaviors\HasProperties;
use DevGroup\DataStructure\search\base\SearchableEntity;
use DevGroup\DataStructure\traits\PropertiesTrait;
use DevGroup\Entity\traits\EntityTrait;
use DevGroup\Entity\traits\SeoTrait;
use DevGroup\Entity\traits\SoftDeleteTrait;
use DevGroup\Multilingual\behaviors\MultilingualActiveRecord;
use DevGroup\Multilingual\models\Context;
use DevGroup\Multilingual\traits\MultilingualTrait;
use DevGroup\TagDependencyHelper\CacheableActiveRecord;
use DevGroup\TagDependencyHelper\TagDependencyTrait;
use DotPlant\EntityStructure\interfaces\MainEntitySeoInterface;
use DotPlant\EntityStructure\models\BaseStructure;
use DotPlant\EntityStructure\traits\MainEntitySeoTrait;
use DotPlant\Monster\Universal\MonsterEntityTrait;
use DotPlant\Store\exceptions\GoodsException;
use DotPlant\Store\interfaces\GoodsInterface;
use DotPlant\Store\interfaces\GoodsTypesInterface;
use DotPlant\Store\interfaces\PriceInterface;
use DotPlant\Store\models\price\Price;
use DotPlant\Store\models\vendor\Vendor;
use DotPlant\Store\repository\GoodsMainCategoryRepositoryInterface;
use DotPlant\Store\repository\Yii2DbGoodsMainCategory;
use Yii;
use yii\data\ActiveDataProvider;
use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
use yii\helpers\ArrayHelper;

/**
 * This is the model class for table "{{%dotplant_goods}}".
 *
 * @property integer $id
 * @property integer $seller_id
 * @property integer $vendor_id
 * @property integer $type
 * @property integer $role
 * @property string $sku
 * @property string $inner_sku
 * @property integer $is_deleted
 * @property integer $created_at
 * @property integer $created_by
 * @property integer $updated_at
 * @property integer $updated_by
 *
 * @property Goods[] $children
 * @property Goods[] $parents
 * @property BaseStructure[] $categories
 * @property BaseStructure $mainCategory
 * @property GoodsTranslation $defaultTranslation
 */
class Goods extends ActiveRecord implements GoodsInterface, GoodsTypesInterface, MainEntitySeoInterface,
    SearchableEntity
{
    use MultilingualTrait;
    use TagDependencyTrait;
    use PropertiesTrait;
    use FetchModels;
    use EntityTrait;
    use SoftDeleteTrait;
    use SeoTrait;
    use MonsterEntityTrait;
    use MainEntitySeoTrait;


    /**
     *
     * @var null
     */
    protected $priceClass = null;

    /**
     * @var null
     */
    protected $hasChild = null;

    /**
     * @var null
     */
    protected $visibilityType = null;


    /**
     * Whether can we apply measures for product
     *
     * @var bool | null
     */
    protected $isMeasurable = null;

    /**
     * Whether can we download product
     *
     * @var bool | null
     */
    protected $isDownloadable = null;

    /**
     * Whether can use product if filters or in search
     *
     * @var bool | null
     */
    protected $isFilterable = null;

    /**
     * Whether product is option
     *
     * @var bool | null
     */
    protected $isService = null;

    /**
     * Whether product is option
     *
     * @var bool | null
     */
    protected $isOption = null;

    /**
     * Whether product is part
     *
     * @var bool | null
     */
    protected $isPart = null;

    /**
     * Whether product has options
     *
     * @var bool | null
     */
    protected $hasOptions = null;

    /** @var  Price | null */
    protected $price = null;

    /**
     * @inheritdoc
     */
    protected static $tablePrefix = 'dotplant_store_goods';

    /**
     * @var GoodsMainCategoryRepositoryInterface
     */
    protected $mainCategoryRepository;


    /**
     * Type to class associations
     *
     * @var array
     */
    private static $_goodsMap = [
        self::TYPE_PRODUCT => Product::class,
        self::TYPE_BUNDLE => Bundle::class,
        self::TYPE_SET => Set::class,
        self::TYPE_PART => Part::class,
        self::TYPE_OPTION => Option::class,
        self::TYPE_SERVICE => Service::class,
        self::TYPE_FILE => File::class,
    ];

    /**
     * Workaround for DataStructureTools to store all goods properties it the one table set
     *
     * @return mixed
     */
    public static function getApplicableClass()
    {
        return self::class;
    }

    /**
     * @return null
     */
    public function getHasChild()
    {
        return $this->hasChild;
    }

    /**
     * @return bool|null
     */
    public function getIsMeasurable()
    {
        return $this->isMeasurable;
    }

    /**
     * @return bool|null
     */
    public function getIsDownloadable()
    {
        return $this->isDownloadable;
    }

    /**
     * @return null
     */
    public function getVisibilityType()
    {
        return $this->visibilityType;
    }

    /**
     * @return bool|null
     */
    public function getIsFilterable()
    {
        return $this->isFilterable;
    }

    /**
     * @return bool|null
     */
    public function getIsService()
    {
        return $this->isService;
    }

    /**
     * @return bool|null
     */
    public function getIsOption()
    {
        return $this->isOption;
    }

    /**
     * @return bool|null
     */
    public function getIsPart()
    {
        return $this->isPart;
    }

    /**
     * @return bool|null
     */
    public function getHasOptions()
    {
        return $this->hasOptions;
    }

    /**
     * @return array
     */
    public static function getTypesMap()
    {
        return self::$_goodsMap;
    }

    /**
     * @return array
     */
    public static function getChildTypes()
    {
        return [];
    }


    /**
     * @inheritdoc
     */
    public static function getRoles()
    {
        return [
            self::TYPE_PART => Yii::t('dotplant.store', 'Part'),
            self::TYPE_OPTION => Yii::t('dotplant.store', 'Option'),
        ];
    }

    /**
     * @inheritdoc
     */
    public static function getTypes()
    {
        return [
            self::TYPE_PRODUCT => Yii::t('dotplant.store', 'Product'),
            self::TYPE_BUNDLE => Yii::t('dotplant.store', 'Bundle'),
            self::TYPE_SET => Yii::t('dotplant.store', 'Set'),
            //  self::TYPE_PART => Yii::t('dotplant.store', 'Part'),
            self::TYPE_OPTION => Yii::t('dotplant.store', 'Option'),
            //  self::TYPE_SERVICE => Yii::t('dotplant.store', 'Service'),
            //  self::TYPE_FILE => Yii::t('dotplant.store', 'File'),
        ];
    }

    /**
     * @inheritdoc
     */
    public function getType()
    {
        return empty($this->role) ? $this->type : $this->role;
    }

    /**
     * @inheritdoc
     */
    public function setType($type)
    {
        if (true === in_array($type, static::getTypes())) {
            $this->type = $type;
            return true;
        }
        return false;
    }

    /**
     * @inheritdoc
     */
    public function setRole($role)
    {
        if (true === in_array($role, static::getRoles())) {
            $this->role = $role;
            return true;
        }
        return false;
    }

    /**
     * @param $warehouseId
     * @param string $priceType
     * @param bool|true $withDiscount
     * @param bool|false $convertIsoCode
     *
     * @return mixed
     */
    public function getPrice(
        $warehouseId,
        $priceType = PriceInterface::TYPE_RETAIL,
        $withDiscount = true,
        $convertIsoCode = false
    ) {
        return $this->price->getPrice($warehouseId, $priceType, $withDiscount, $convertIsoCode);
    }

    /**
     * @param string $priceType
     * @param bool|true $withDiscount
     * @param bool|false $convertIsoCode
     *
     * @return mixed
     */
    public function getMinPrice(
        $priceType = PriceInterface::TYPE_RETAIL,
        $withDiscount = true,
        $convertIsoCode = false,
        $contextId = null
    ) {
        return $this->price->getMinPrice($priceType, $withDiscount, $convertIsoCode, $contextId);
    }

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%dotplant_store_goods}}';
    }

    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'multilingual' => [
                'class' => MultilingualActiveRecord::class,
                'translationModelClass' => GoodsTranslation::class,
                'translationPublishedAttribute' => 'is_active',
            ],
            'CacheableActiveRecord' => [
                'class' => CacheableActiveRecord::class,
            ],
            'properties' => [
                'class' => HasProperties::class,
                                'autoFetchProperties' => true,
            ],
        ];
    }

    /**
     * @inheritdoc
     */
    public function getRules()
    {
        return ArrayHelper::merge(
            [
                [
                    [
                        'vendor_id',
                        'type',
                        'role',
                    ],
                    'integer',
                ],
                [['sku',], 'required'],
                [['sku', 'inner_sku'], 'string', 'max' => 255],
                [
                    ['vendor_id'],
                    'exist',
                    'skipOnError' => true,
                    'targetClass' => Vendor::class,
                    'targetAttribute' => ['vendor_id' => 'id'],
                ],
                [['asin',], 'string', 'max' => 10],
                [['isbn',], 'string', 'max' => 18],
                [['ean', 'upc', 'jan'], 'string', 'max' => 13],
            ],
            $this->propertiesRules()
        );
    }

    /**
     * @inheritdoc
     */
    public function getAttributeLabels()
    {
        return [
            'id' => Yii::t('dotplant.store', 'ID'),
            'seller_id' => Yii::t('dotplant.store', 'Seller'),
            'vendor_id' => Yii::t('dotplant.store', 'Vendor'),
            'type' => Yii::t('dotplant.store', 'Type'),
            'role' => Yii::t('dotplant.store', 'Role'),
            'sku' => Yii::t('dotplant.store', 'Sku'),
            'inner_sku' => Yii::t('dotplant.store', 'Inner sku'),
            'is_deleted' => Yii::t('dotplant.store', 'Is deleted'),
            'created_at' => Yii::t('dotplant.store', 'Created at'),
            'created_by' => Yii::t('dotplant.store', 'Created by'),
            'updated_at' => Yii::t('dotplant.store', 'Updated at'),
            'updated_by' => Yii::t('dotplant.store', 'Updated by'),
            'asin' => Yii::t('dotplant.store', 'ASIN'),
            'isbn' => Yii::t('dotplant.store', 'ISBN'),
            'ean' => Yii::t('dotplant.store', 'EAN'),
            'upc' => Yii::t('dotplant.store', 'UPC'),
            'jan' => Yii::t('dotplant.store', 'JAN'),
        ];
    }

    /**
     * @return array
     */
    public function getAttributeHints()
    {
        return [
            'asin' => Yii::t('dotplant.store', 'Amazon Standard Identification Number'),
            'isbn' => Yii::t('dotplant.store', 'International Standard Book Number'),
            'ean' => Yii::t('dotplant.store', 'European Article Number'),
            'upc' => Yii::t('dotplant.store', 'Universal Product Code'),
            'jan' => Yii::t('dotplant.store', 'Japanese Article Number'),
        ];
    }

    /**
     * Override safe attributes to include translation attributes
     *
     * @return array
     */
    public function safeAttributes()
    {
        $t = new GoodsTranslation();
        return ArrayHelper::merge(parent::safeAttributes(), $t->safeAttributes());
    }

    /**
     * Override for filtering in grid
     *
     * @param string $attribute
     *
     * @return bool
     */
    public function isAttributeActive($attribute)
    {
        return in_array($attribute, $this->safeAttributes());
    }

    /**
     * Override Multilingual find method to include unpublished records
     *
     * @return ActiveQuery
     * @throws \yii\base\InvalidConfigException
     */
    public static function find()
    {
        /** @var ActiveQuery $query */
        $query = Yii::createObject(ActiveQuery::className(), [get_called_class()]);
        return $query = $query->innerJoinWith(['defaultTranslation']);
    }

    /**
     * @return ActiveQuery
     */
    public function getParents()
    {
        return $this->hasMany(static::class, ['id' => 'goods_parent_id'])->viaTable(
                GoodsParent::tableName(),
                ['goods_id' => 'id']
            );
    }

    /**
     * @return ActiveQuery
     */
    public function getChildren()
    {
        return $this->hasMany(static::class, ['id' => 'goods_id'])->viaTable(
                GoodsParent::tableName(),
                ['goods_parent_id' => 'id']
            );
    }

    /**
     * @return ActiveQuery
     */
    public function getCategories()
    {
        return $this->hasMany(BaseStructure::class, ['id' => 'structure_id'])->viaTable(
                CategoryGoods::tableName(),
                ['goods_id' => 'id']
            );
    }

    /**
     * @return ActiveQuery
     */
    public function getMainCategory()
    {
        return $this->hasOne(BaseStructure::class, ['id' => 'main_structure_id'])->viaTable(
            Yii2DbGoodsMainCategory::TABLE_NAME,
            ['goods_id' => 'id']
        )->where(['context_id' => Yii::$app->multilingual->context_id]);
//        return $this->getMainStructure(Yii::$app->multilingual->context_id);
    }

    /**
     * @inheritdoc
     */
    public static function create($type)
    {
        if (false === isset(static::getTypes()[$type])) {
            throw new GoodsException(
                Yii::t('dotplant.store', 'Attempting to create unknown type of goods')
            );
        }
        $goodsClass = self::$_goodsMap[$type];
        /** @var Goods $goods */
        $goods = new $goodsClass;
        $goods->type = $type;
        self::injectPriceObject($goods);
        return $goods;
    }

    public static function instantiate($row)
    {
        $type = empty($row['role']) ? $row['type'] : $row['role'];
        if (false === isset(self::$_goodsMap[$type])) {
            throw new GoodsException(
                Yii::t('dotplant.store', 'Attempting to create unknown type of goods')
            );
        }
        $productClass = self::$_goodsMap[$type];
        /** @var self $model */
        $goods = new $productClass;
        self::populateRecord($goods, $row);
        self::injectPriceObject($goods);
        return $goods;
    }


    /**
     * Injects according Price object into Goods model for further calculations
     *
     * @param $goods
     *
     * @throws GoodsException
     * @throws \DotPlant\Store\exceptions\PriceException
     */
    private static function injectPriceObject($goods)
    {
        if (null !== $goods->priceClass) {
            /** @var Price $priceClass */
            $priceClass = $goods->priceClass;
            $goods->price = $priceClass::create($goods);
        } else {
            throw new GoodsException(
                Yii::t('dotplant.store', '\'priceClass\' property myst be valid heir of Price')
            );
        }
    }

    /**
     * Finds models
     *
     * @param $params
     *
     * @return ActiveDataProvider
     */
    public function search($params, $categoryId = null, $query = null)
    {
        /* @var $query ActiveQuery */
        if ($query === null) {
            $query = static::find();
        }
        $dataProvider = new ActiveDataProvider(
            [
                'query' => $query,
                'pagination' => [
                    //TODO configure it
                    'pageSize' => 15,
                ],
            ]
        );
        if (null !== $categoryId) {
            $query->innerJoin(
                CategoryGoods::tableName(),
                'id = ' . CategoryGoods::tableName() . '.goods_id'
            )->andWhere(['structure_id' => $categoryId]);
        }

        $dataProvider->sort->attributes['name'] = [
            'asc' => [GoodsTranslation::tableName() . '.name' => SORT_ASC],
            'desc' => [GoodsTranslation::tableName() . '.name' => SORT_DESC],
        ];
        $dataProvider->sort->attributes['title'] = [
            'asc' => [GoodsTranslation::tableName() . '.title' => SORT_ASC],
            'desc' => [GoodsTranslation::tableName() . '.title' => SORT_DESC],
        ];
        $dataProvider->sort->attributes['is_active'] = [
            'asc' => [GoodsTranslation::tableName() . '.is_active' => SORT_ASC],
            'desc' => [GoodsTranslation::tableName() . '.is_active' => SORT_DESC],
        ];
        $dataProvider->sort->attributes['slug'] = [
            'asc' => [GoodsTranslation::tableName() . '.slug' => SORT_ASC],
            'desc' => [GoodsTranslation::tableName() . '.slug' => SORT_DESC],
        ];
        if (false === $this->load($params)) {
            return $dataProvider;
        }
        $query->andFilterWhere(['id' => $this->id]);
        $query->andFilterWhere(['is_deleted' => $this->is_deleted]);
        $query->andFilterWhere(['type' => $this->type]);
        $translation = new GoodsTranslation();
        if (false === $translation->load(static::fetchParams($params, static::class, $translation))) {
            return $dataProvider;
        }
        $query->andFilterWhere(['like', GoodsTranslation::tableName() . '.name', $this->name]);
        $query->andFilterWhere(['like', GoodsTranslation::tableName() . '.title', $this->title]);
        $query->andFilterWhere(['like', GoodsTranslation::tableName() . '.h1', $this->h1]);
        $query->andFilterWhere(['like', GoodsTranslation::tableName() . '.slug', $this->slug]);
        $query->andFilterWhere([GoodsTranslation::tableName() . '.is_active' => $this->is_active]);
        return $dataProvider;
    }

    /**
     * @return array
     * @throws \yii\base\InvalidConfigException
     */
    public function advancedTranslatableAttributes()
    {
        $result = array_keys(GoodsExtended::getTableSchema()->columns);
        $result[] = 'content';
        $result[] = 'providers';
        return $result;
    }

    /**
     * @inheritdoc
     */
    public function getSeoBreadcrumbs()
    {
        $model = $this->getMainStructure(Yii::$app->multilingual->context_id);
        /** @var array $breadcrumbs */
        $breadcrumbs = $model->getSeoBreadcrumbs();
        $breadcrumbs[] = [
            'label' => $this->defaultTranslation->breadcrumbs_label,
        ];
        return $breadcrumbs;
    }

    /**
     * Returns properly instantiated Goods model if found
     *
     * @param int $id
     *
     * @return Goods
     * @throws GoodsException
     */
    public static function get($id)
    {
        return static::loadModel($id, false, true, 86400, false, true);
    }

    public function afterFind()
    {
        parent::afterFind();
        static::$identityMap[$this->id] = &$this;
    }

    /**
     * @inheritdoc
     */
    public static function defaultAttributesScope()
    {
        return [
            'is_deleted' => 0,
            self::getTranslationTableName() . '.is_active' => 1,
        ];
    }

    public static function defaultWith()
    {
        return [
            'mainCategory',
            'mainCategory.defaultTranslation',
            'defaultTranslation.extended',
        ];
    }

    public function getMainStructures()
    {
        $result = [];
        $contexts = call_user_func([Yii::$app->multilingual->modelsMap['Context'], 'getListData']);
        foreach ($contexts as $key => $item) {
            $structureId = $key === '' ? null : $key;
            $result[intval($structureId)] = $this->getMainStructure($structureId);
        }
        return $result;
    }

    public function setMainStructures($structures = [])
    {
        foreach ($structures as $key => $structureId) {
            $contextId = $key == 0 ? null : intval($key);
            if (!empty($structureId)) {
                $this->setMainStructure(BaseStructure::findOne($structureId), $contextId);
            }
        }
    }

    public function getMainStructure($contextId)
    {
        return $this->getMainStructureRepository()->loadGoodsMainCategory($contextId);
    }

    public function setMainStructure(BaseStructure $structure, $contextId)
    {
        $this->getMainStructureRepository()->setMainCategory($structure, $contextId);
    }

    protected function getMainStructureRepository()
    {
        if ($this->mainCategoryRepository === null) {
            $this->mainCategoryRepository = Yii::$container->get(
                GoodsMainCategoryRepositoryInterface::class,
                [$this->id]
            );
        }
        return $this->mainCategoryRepository;
    }
}
<commit_msg>the lost trait has been found<commit_after><?php

namespace DotPlant\Store\models\goods;

use DevGroup\AdminUtils\traits\FetchModels;
use DevGroup\DataStructure\behaviors\HasProperties;
use DevGroup\DataStructure\search\base\SearchableEntity;
use DevGroup\DataStructure\traits\PropertiesTrait;
use DevGroup\Entity\traits\BaseActionsInfoTrait;
use DevGroup\Entity\traits\EntityTrait;
use DevGroup\Entity\traits\SeoTrait;
use DevGroup\Entity\traits\SoftDeleteTrait;
use DevGroup\Multilingual\behaviors\MultilingualActiveRecord;
use DevGroup\Multilingual\models\Context;
use DevGroup\Multilingual\traits\MultilingualTrait;
use DevGroup\TagDependencyHelper\CacheableActiveRecord;
use DevGroup\TagDependencyHelper\TagDependencyTrait;
use DotPlant\EntityStructure\interfaces\MainEntitySeoInterface;
use DotPlant\EntityStructure\models\BaseStructure;
use DotPlant\EntityStructure\traits\MainEntitySeoTrait;
use DotPlant\Monster\Universal\MonsterEntityTrait;
use DotPlant\Store\exceptions\GoodsException;
use DotPlant\Store\interfaces\GoodsInterface;
use DotPlant\Store\interfaces\GoodsTypesInterface;
use DotPlant\Store\interfaces\PriceInterface;
use DotPlant\Store\models\price\Price;
use DotPlant\Store\models\vendor\Vendor;
use DotPlant\Store\repository\GoodsMainCategoryRepositoryInterface;
use DotPlant\Store\repository\Yii2DbGoodsMainCategory;
use Yii;
use yii\data\ActiveDataProvider;
use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
use yii\helpers\ArrayHelper;

/**
 * This is the model class for table "{{%dotplant_goods}}".
 *
 * @property integer $id
 * @property integer $seller_id
 * @property integer $vendor_id
 * @property integer $type
 * @property integer $role
 * @property string $sku
 * @property string $inner_sku
 * @property integer $is_deleted
 * @property integer $created_at
 * @property integer $created_by
 * @property integer $updated_at
 * @property integer $updated_by
 *
 * @property Goods[] $children
 * @property Goods[] $parents
 * @property BaseStructure[] $categories
 * @property BaseStructure $mainCategory
 * @property GoodsTranslation $defaultTranslation
 */
class Goods extends ActiveRecord implements GoodsInterface, GoodsTypesInterface, MainEntitySeoInterface,
    SearchableEntity
{
    use MultilingualTrait;
    use TagDependencyTrait;
    use PropertiesTrait;
    use FetchModels;
    use EntityTrait;
    use SoftDeleteTrait;
    use SeoTrait;
    use MonsterEntityTrait;
    use MainEntitySeoTrait;
    use BaseActionsInfoTrait;

    /**
     *
     * @var null
     */
    protected $priceClass = null;

    /**
     * @var null
     */
    protected $hasChild = null;

    /**
     * @var null
     */
    protected $visibilityType = null;


    /**
     * Whether can we apply measures for product
     *
     * @var bool | null
     */
    protected $isMeasurable = null;

    /**
     * Whether can we download product
     *
     * @var bool | null
     */
    protected $isDownloadable = null;

    /**
     * Whether can use product if filters or in search
     *
     * @var bool | null
     */
    protected $isFilterable = null;

    /**
     * Whether product is option
     *
     * @var bool | null
     */
    protected $isService = null;

    /**
     * Whether product is option
     *
     * @var bool | null
     */
    protected $isOption = null;

    /**
     * Whether product is part
     *
     * @var bool | null
     */
    protected $isPart = null;

    /**
     * Whether product has options
     *
     * @var bool | null
     */
    protected $hasOptions = null;

    /** @var  Price | null */
    protected $price = null;

    /**
     * @inheritdoc
     */
    protected static $tablePrefix = 'dotplant_store_goods';

    /**
     * @var GoodsMainCategoryRepositoryInterface
     */
    protected $mainCategoryRepository;


    /**
     * Type to class associations
     *
     * @var array
     */
    private static $_goodsMap = [
        self::TYPE_PRODUCT => Product::class,
        self::TYPE_BUNDLE => Bundle::class,
        self::TYPE_SET => Set::class,
        self::TYPE_PART => Part::class,
        self::TYPE_OPTION => Option::class,
        self::TYPE_SERVICE => Service::class,
        self::TYPE_FILE => File::class,
    ];

    /**
     * Workaround for DataStructureTools to store all goods properties it the one table set
     *
     * @return mixed
     */
    public static function getApplicableClass()
    {
        return self::class;
    }

    /**
     * @return null
     */
    public function getHasChild()
    {
        return $this->hasChild;
    }

    /**
     * @return bool|null
     */
    public function getIsMeasurable()
    {
        return $this->isMeasurable;
    }

    /**
     * @return bool|null
     */
    public function getIsDownloadable()
    {
        return $this->isDownloadable;
    }

    /**
     * @return null
     */
    public function getVisibilityType()
    {
        return $this->visibilityType;
    }

    /**
     * @return bool|null
     */
    public function getIsFilterable()
    {
        return $this->isFilterable;
    }

    /**
     * @return bool|null
     */
    public function getIsService()
    {
        return $this->isService;
    }

    /**
     * @return bool|null
     */
    public function getIsOption()
    {
        return $this->isOption;
    }

    /**
     * @return bool|null
     */
    public function getIsPart()
    {
        return $this->isPart;
    }

    /**
     * @return bool|null
     */
    public function getHasOptions()
    {
        return $this->hasOptions;
    }

    /**
     * @return array
     */
    public static function getTypesMap()
    {
        return self::$_goodsMap;
    }

    /**
     * @return array
     */
    public static function getChildTypes()
    {
        return [];
    }


    /**
     * @inheritdoc
     */
    public static function getRoles()
    {
        return [
            self::TYPE_PART => Yii::t('dotplant.store', 'Part'),
            self::TYPE_OPTION => Yii::t('dotplant.store', 'Option'),
        ];
    }

    /**
     * @inheritdoc
     */
    public static function getTypes()
    {
        return [
            self::TYPE_PRODUCT => Yii::t('dotplant.store', 'Product'),
            self::TYPE_BUNDLE => Yii::t('dotplant.store', 'Bundle'),
            self::TYPE_SET => Yii::t('dotplant.store', 'Set'),
            //  self::TYPE_PART => Yii::t('dotplant.store', 'Part'),
            self::TYPE_OPTION => Yii::t('dotplant.store', 'Option'),
            //  self::TYPE_SERVICE => Yii::t('dotplant.store', 'Service'),
            //  self::TYPE_FILE => Yii::t('dotplant.store', 'File'),
        ];
    }

    /**
     * @inheritdoc
     */
    public function getType()
    {
        return empty($this->role) ? $this->type : $this->role;
    }

    /**
     * @inheritdoc
     */
    public function setType($type)
    {
        if (true === in_array($type, static::getTypes())) {
            $this->type = $type;
            return true;
        }
        return false;
    }

    /**
     * @inheritdoc
     */
    public function setRole($role)
    {
        if (true === in_array($role, static::getRoles())) {
            $this->role = $role;
            return true;
        }
        return false;
    }

    /**
     * @param $warehouseId
     * @param string $priceType
     * @param bool|true $withDiscount
     * @param bool|false $convertIsoCode
     *
     * @return mixed
     */
    public function getPrice(
        $warehouseId,
        $priceType = PriceInterface::TYPE_RETAIL,
        $withDiscount = true,
        $convertIsoCode = false
    ) {
        return $this->price->getPrice($warehouseId, $priceType, $withDiscount, $convertIsoCode);
    }

    /**
     * @param string $priceType
     * @param bool|true $withDiscount
     * @param bool|false $convertIsoCode
     *
     * @return mixed
     */
    public function getMinPrice(
        $priceType = PriceInterface::TYPE_RETAIL,
        $withDiscount = true,
        $convertIsoCode = false,
        $contextId = null
    ) {
        return $this->price->getMinPrice($priceType, $withDiscount, $convertIsoCode, $contextId);
    }

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%dotplant_store_goods}}';
    }

    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'multilingual' => [
                'class' => MultilingualActiveRecord::class,
                'translationModelClass' => GoodsTranslation::class,
                'translationPublishedAttribute' => 'is_active',
            ],
            'CacheableActiveRecord' => [
                'class' => CacheableActiveRecord::class,
            ],
            'properties' => [
                'class' => HasProperties::class,
                                'autoFetchProperties' => true,
            ],
        ];
    }

    /**
     * @inheritdoc
     */
    public function getRules()
    {
        return ArrayHelper::merge(
            [
                [
                    [
                        'vendor_id',
                        'type',
                        'role',
                    ],
                    'integer',
                ],
                [['sku',], 'required'],
                [['sku', 'inner_sku'], 'string', 'max' => 255],
                [
                    ['vendor_id'],
                    'exist',
                    'skipOnError' => true,
                    'targetClass' => Vendor::class,
                    'targetAttribute' => ['vendor_id' => 'id'],
                ],
                [['asin',], 'string', 'max' => 10],
                [['isbn',], 'string', 'max' => 18],
                [['ean', 'upc', 'jan'], 'string', 'max' => 13],
            ],
            $this->propertiesRules()
        );
    }

    /**
     * @inheritdoc
     */
    public function getAttributeLabels()
    {
        return [
            'id' => Yii::t('dotplant.store', 'ID'),
            'seller_id' => Yii::t('dotplant.store', 'Seller'),
            'vendor_id' => Yii::t('dotplant.store', 'Vendor'),
            'type' => Yii::t('dotplant.store', 'Type'),
            'role' => Yii::t('dotplant.store', 'Role'),
            'sku' => Yii::t('dotplant.store', 'Sku'),
            'inner_sku' => Yii::t('dotplant.store', 'Inner sku'),
            'is_deleted' => Yii::t('dotplant.store', 'Is deleted'),
            'created_at' => Yii::t('dotplant.store', 'Created at'),
            'created_by' => Yii::t('dotplant.store', 'Created by'),
            'updated_at' => Yii::t('dotplant.store', 'Updated at'),
            'updated_by' => Yii::t('dotplant.store', 'Updated by'),
            'asin' => Yii::t('dotplant.store', 'ASIN'),
            'isbn' => Yii::t('dotplant.store', 'ISBN'),
            'ean' => Yii::t('dotplant.store', 'EAN'),
            'upc' => Yii::t('dotplant.store', 'UPC'),
            'jan' => Yii::t('dotplant.store', 'JAN'),
        ];
    }

    /**
     * @return array
     */
    public function getAttributeHints()
    {
        return [
            'asin' => Yii::t('dotplant.store', 'Amazon Standard Identification Number'),
            'isbn' => Yii::t('dotplant.store', 'International Standard Book Number'),
            'ean' => Yii::t('dotplant.store', 'European Article Number'),
            'upc' => Yii::t('dotplant.store', 'Universal Product Code'),
            'jan' => Yii::t('dotplant.store', 'Japanese Article Number'),
        ];
    }

    /**
     * Override safe attributes to include translation attributes
     *
     * @return array
     */
    public function safeAttributes()
    {
        $t = new GoodsTranslation();
        return ArrayHelper::merge(parent::safeAttributes(), $t->safeAttributes());
    }

    /**
     * Override for filtering in grid
     *
     * @param string $attribute
     *
     * @return bool
     */
    public function isAttributeActive($attribute)
    {
        return in_array($attribute, $this->safeAttributes());
    }

    /**
     * Override Multilingual find method to include unpublished records
     *
     * @return ActiveQuery
     * @throws \yii\base\InvalidConfigException
     */
    public static function find()
    {
        /** @var ActiveQuery $query */
        $query = Yii::createObject(ActiveQuery::className(), [get_called_class()]);
        return $query = $query->innerJoinWith(['defaultTranslation']);
    }

    /**
     * @return ActiveQuery
     */
    public function getParents()
    {
        return $this->hasMany(static::class, ['id' => 'goods_parent_id'])->viaTable(
                GoodsParent::tableName(),
                ['goods_id' => 'id']
            );
    }

    /**
     * @return ActiveQuery
     */
    public function getChildren()
    {
        return $this->hasMany(static::class, ['id' => 'goods_id'])->viaTable(
                GoodsParent::tableName(),
                ['goods_parent_id' => 'id']
            );
    }

    /**
     * @return ActiveQuery
     */
    public function getCategories()
    {
        return $this->hasMany(BaseStructure::class, ['id' => 'structure_id'])->viaTable(
                CategoryGoods::tableName(),
                ['goods_id' => 'id']
            );
    }

    /**
     * @return ActiveQuery
     */
    public function getMainCategory()
    {
        return $this->hasOne(BaseStructure::class, ['id' => 'main_structure_id'])->viaTable(
            Yii2DbGoodsMainCategory::TABLE_NAME,
            ['goods_id' => 'id']
        )->where(['context_id' => Yii::$app->multilingual->context_id]);
//        return $this->getMainStructure(Yii::$app->multilingual->context_id);
    }

    /**
     * @inheritdoc
     */
    public static function create($type)
    {
        if (false === isset(static::getTypes()[$type])) {
            throw new GoodsException(
                Yii::t('dotplant.store', 'Attempting to create unknown type of goods')
            );
        }
        $goodsClass = self::$_goodsMap[$type];
        /** @var Goods $goods */
        $goods = new $goodsClass;
        $goods->type = $type;
        self::injectPriceObject($goods);
        return $goods;
    }

    public static function instantiate($row)
    {
        $type = empty($row['role']) ? $row['type'] : $row['role'];
        if (false === isset(self::$_goodsMap[$type])) {
            throw new GoodsException(
                Yii::t('dotplant.store', 'Attempting to create unknown type of goods')
            );
        }
        $productClass = self::$_goodsMap[$type];
        /** @var self $model */
        $goods = new $productClass;
        self::populateRecord($goods, $row);
        self::injectPriceObject($goods);
        return $goods;
    }


    /**
     * Injects according Price object into Goods model for further calculations
     *
     * @param $goods
     *
     * @throws GoodsException
     * @throws \DotPlant\Store\exceptions\PriceException
     */
    private static function injectPriceObject($goods)
    {
        if (null !== $goods->priceClass) {
            /** @var Price $priceClass */
            $priceClass = $goods->priceClass;
            $goods->price = $priceClass::create($goods);
        } else {
            throw new GoodsException(
                Yii::t('dotplant.store', '\'priceClass\' property myst be valid heir of Price')
            );
        }
    }

    /**
     * Finds models
     *
     * @param $params
     *
     * @return ActiveDataProvider
     */
    public function search($params, $categoryId = null, $query = null)
    {
        /* @var $query ActiveQuery */
        if ($query === null) {
            $query = static::find();
        }
        $dataProvider = new ActiveDataProvider(
            [
                'query' => $query,
                'pagination' => [
                    //TODO configure it
                    'pageSize' => 15,
                ],
            ]
        );
        if (null !== $categoryId) {
            $query->innerJoin(
                CategoryGoods::tableName(),
                'id = ' . CategoryGoods::tableName() . '.goods_id'
            )->andWhere(['structure_id' => $categoryId]);
        }

        $dataProvider->sort->attributes['name'] = [
            'asc' => [GoodsTranslation::tableName() . '.name' => SORT_ASC],
            'desc' => [GoodsTranslation::tableName() . '.name' => SORT_DESC],
        ];
        $dataProvider->sort->attributes['title'] = [
            'asc' => [GoodsTranslation::tableName() . '.title' => SORT_ASC],
            'desc' => [GoodsTranslation::tableName() . '.title' => SORT_DESC],
        ];
        $dataProvider->sort->attributes['is_active'] = [
            'asc' => [GoodsTranslation::tableName() . '.is_active' => SORT_ASC],
            'desc' => [GoodsTranslation::tableName() . '.is_active' => SORT_DESC],
        ];
        $dataProvider->sort->attributes['slug'] = [
            'asc' => [GoodsTranslation::tableName() . '.slug' => SORT_ASC],
            'desc' => [GoodsTranslation::tableName() . '.slug' => SORT_DESC],
        ];
        if (false === $this->load($params)) {
            return $dataProvider;
        }
        $query->andFilterWhere(['id' => $this->id]);
        $query->andFilterWhere(['is_deleted' => $this->is_deleted]);
        $query->andFilterWhere(['type' => $this->type]);
        $translation = new GoodsTranslation();
        if (false === $translation->load(static::fetchParams($params, static::class, $translation))) {
            return $dataProvider;
        }
        $query->andFilterWhere(['like', GoodsTranslation::tableName() . '.name', $this->name]);
        $query->andFilterWhere(['like', GoodsTranslation::tableName() . '.title', $this->title]);
        $query->andFilterWhere(['like', GoodsTranslation::tableName() . '.h1', $this->h1]);
        $query->andFilterWhere(['like', GoodsTranslation::tableName() . '.slug', $this->slug]);
        $query->andFilterWhere([GoodsTranslation::tableName() . '.is_active' => $this->is_active]);
        return $dataProvider;
    }

    /**
     * @return array
     * @throws \yii\base\InvalidConfigException
     */
    public function advancedTranslatableAttributes()
    {
        $result = array_keys(GoodsExtended::getTableSchema()->columns);
        $result[] = 'content';
        $result[] = 'providers';
        return $result;
    }

    /**
     * @inheritdoc
     */
    public function getSeoBreadcrumbs()
    {
        $model = $this->getMainStructure(Yii::$app->multilingual->context_id);
        /** @var array $breadcrumbs */
        $breadcrumbs = $model->getSeoBreadcrumbs();
        $breadcrumbs[] = [
            'label' => $this->defaultTranslation->breadcrumbs_label,
        ];
        return $breadcrumbs;
    }

    /**
     * Returns properly instantiated Goods model if found
     *
     * @param int $id
     *
     * @return Goods
     * @throws GoodsException
     */
    public static function get($id)
    {
        return static::loadModel($id, false, true, 86400, false, true);
    }

    public function afterFind()
    {
        parent::afterFind();
        static::$identityMap[$this->id] = &$this;
    }

    /**
     * @inheritdoc
     */
    public static function defaultAttributesScope()
    {
        return [
            'is_deleted' => 0,
            self::getTranslationTableName() . '.is_active' => 1,
        ];
    }

    public static function defaultWith()
    {
        return [
            'mainCategory',
            'mainCategory.defaultTranslation',
            'defaultTranslation.extended',
        ];
    }

    public function getMainStructures()
    {
        $result = [];
        $contexts = call_user_func([Yii::$app->multilingual->modelsMap['Context'], 'getListData']);
        foreach ($contexts as $key => $item) {
            $structureId = $key === '' ? null : $key;
            $result[intval($structureId)] = $this->getMainStructure($structureId);
        }
        return $result;
    }

    public function setMainStructures($structures = [])
    {
        foreach ($structures as $key => $structureId) {
            $contextId = $key == 0 ? null : intval($key);
            if (!empty($structureId)) {
                $this->setMainStructure(BaseStructure::findOne($structureId), $contextId);
            }
        }
    }

    public function getMainStructure($contextId)
    {
        return $this->getMainStructureRepository()->loadGoodsMainCategory($contextId);
    }

    public function setMainStructure(BaseStructure $structure, $contextId)
    {
        $this->getMainStructureRepository()->setMainCategory($structure, $contextId);
    }

    protected function getMainStructureRepository()
    {
        if ($this->mainCategoryRepository === null) {
            $this->mainCategoryRepository = Yii::$container->get(
                GoodsMainCategoryRepositoryInterface::class,
                [$this->id]
            );
        }
        return $this->mainCategoryRepository;
    }
}

{'fraction_non_alphanumeric': 0.14615899421578468, 'fraction_numerical': 0.0010994789425880778, 'mean_word_length': 2.0624359537403016, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 367, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3596695', 'n_tokens_mistral': 12364, 'n_tokens_neox': 12065, 'n_words': 2317}