Commit b519af228167867d15d4e91e1113387838daed3f

Authored by Karnovsky A
1 parent 9d33ce37

Base-product functional

common/components/artboxtree/ArtboxTreeBehavior.php
... ... @@ -20,6 +20,7 @@ class ArtboxTreeBehavior extends Behavior {
20 20 public $keyNameGroup = 'group';
21 21 public $keyNamePath = 'path_int';
22 22 public $keyNameDepth = 'depth'; // @todo -> $keyNameDepth;
  23 + public $primaryKeyMode = true;
23 24  
24 25 /**
25 26 * @var string
... ... @@ -88,8 +89,31 @@ class ArtboxTreeBehavior extends Behavior {
88 89 * get all-level children items
89 90 * use MP-method
90 91 */
91   - public function getAllChildren($depth = null) {
92   - return $this->getAllChildrenMP($depth);
  92 + public function getAllChildren($depth = null, $where = []) {
  93 + return $this->getAllChildrenMP($depth)->where($where);
  94 + }
  95 +
  96 + /*
  97 + * get all-level children items
  98 + * use MP-method
  99 + */
  100 + public function getAllChildrenTree($depth = null, $where = []) {
  101 + return $this->buildTree($this->getAllChildrenMP($depth, $where)->all(), $this->owner->getAttribute($this->keyNameId));
  102 + }
  103 +
  104 + protected function buildTree(array $data, $parentId = 0) {
  105 + $result = [];
  106 + foreach ($data as $key => $element) {
  107 + if ($element->getAttribute($this->keyNameParentId) == $parentId) {
  108 + unset($data[$key]);
  109 + $children = $this->buildTree($data, $element->getAttribute($this->keyNameId));
  110 + $result[] = [
  111 + 'item' => $element,
  112 + 'children' => $children
  113 + ];
  114 + }
  115 + }
  116 + return $result;
93 117 }
94 118  
95 119  
... ... @@ -103,9 +127,30 @@ class ArtboxTreeBehavior extends Behavior {
103 127 * Full-path (use MP-method)
104 128 */
105 129 public function getParentsMP($depth = null) {
  130 + $tableName = $this->owner->tableName();
  131 + $path = $this->owner->getAttribute($this->keyNamePath);
  132 + $query = $this->owner->find()
  133 + ->andWhere(['<@', "{$tableName}.[[{$this->keyNamePath}]]", $path]);
  134 + if ($depth > 0) {
  135 + $query->andWhere(['>=', "{$tableName}.[[{$this->keyNameDepth}]]", $this->owner->getAttribute($this->keyNameDepth) - $depth]);
  136 + }
  137 + $query->andWhere(['<', "{$tableName}.[[{$this->keyNameDepth}]]", $this->owner->getAttribute($this->keyNameDepth)]);
  138 +
  139 + $orderBy = [];
  140 + $orderBy["{$tableName}.[[{$this->keyNameDepth}]]"] = SORT_ASC;
  141 + $orderBy["{$tableName}.[[{$this->keyNameId}]]"] = SORT_ASC;
  142 +
  143 + $query
  144 + ->andWhere($this->groupWhere())
  145 + ->addOrderBy($orderBy);
  146 + $query->multiple = true;
  147 +
  148 + return $query;
  149 + }
  150 + /*public function getParentsMP($depth = null) {
106 151 $path = $this->getParentPath();
107 152 if ($path !== null) {
108   - $paths = str_replace(['{', '}'], '', explode(',', $path));
  153 + $paths = explode(',', trim($path, '{}'));
109 154 if (!$this->primaryKeyMode) {
110 155 $path = null;
111 156 $paths = array_map(
... ... @@ -123,7 +168,6 @@ class ArtboxTreeBehavior extends Behavior {
123 168 }
124 169  
125 170 $tableName = $this->owner->tableName();
126   - $condition = ['and'];
127 171 if ($this->primaryKeyMode) {
128 172 $condition[] = ["{$tableName}.[[{$this->keyNameId}]]" => $paths];
129 173 } else {
... ... @@ -132,12 +176,12 @@ class ArtboxTreeBehavior extends Behavior {
132 176  
133 177 $query = $this->owner->find()
134 178 ->andWhere($condition)
135   - ->andWhere($this->treeCondition())
  179 + ->andWhere($this->groupWhere())
136 180 ->addOrderBy(["{$tableName}.[[{$this->keyNamePath}]]" => SORT_ASC]);
137 181 $query->multiple = true;
138 182  
139 183 return $query;
140   - }
  184 + }*/
141 185  
142 186 /**
143 187 * @param bool $asArray = false
... ... @@ -147,14 +191,26 @@ class ArtboxTreeBehavior extends Behavior {
147 191 {
148 192 return static::getParentPathInternal($this->owner->getAttribute($this->keyNamePath), $asArray);
149 193 }
  194 + /**
  195 + * @return array
  196 + */
  197 + protected function groupWhere()
  198 + {
  199 + $tableName = $this->owner->tableName();
  200 + if ($this->keyNameGroup === null) {
  201 + return [];
  202 + } else {
  203 + return ["{$tableName}.[[{$this->keyNameGroup}]]" => $this->owner->getAttribute($this->keyNameGroup)];
  204 + }
  205 + }
  206 +
150 207  
151 208 public function getAllChildrenMP($depth = null)
152 209 {
153 210 $tableName = $this->owner->tableName();
154 211 $path = $this->owner->getAttribute($this->keyNamePath);
155 212 $query = $this->owner->find()
156   - ->andWhere(['@>', "{$tableName}.[[{$this->keyNamePath}]]", $this->getLike($path), false]);
157   -
  213 + ->andWhere(['@>', "{$tableName}.[[{$this->keyNamePath}]]", $path]);
158 214  
159 215 if ($depth > 0) {
160 216 $query->andWhere(['<=', "{$tableName}.[[{$this->keyNameDepth}]]", $this->owner->getAttribute($this->keyNameDepth) + $depth]);
... ... @@ -165,7 +221,7 @@ class ArtboxTreeBehavior extends Behavior {
165 221 $orderBy["{$tableName}.[[{$this->keyNameId}]]"] = SORT_ASC;
166 222  
167 223 $query
168   - ->andWhere($this->treeCondition())
  224 + ->andWhere($this->groupWhere())
169 225 ->addOrderBy($orderBy);
170 226 $query->multiple = true;
171 227  
... ... @@ -186,7 +242,13 @@ class ArtboxTreeBehavior extends Behavior {
186 242 $parent_id = $this->owner->getAttribute($this->keyNameParentId);
187 243 if (empty($parent_id))
188 244 return null;
189   - return $this->owner->find()->where([$this->keyNameId => $parent_id, $this->keyNameGroup => $this->owner->getAttribute($this->keyNameGroup)])->one();
  245 +
  246 + $where = [$this->keyNameId => $parent_id];
  247 + if ($this->keyNameGroup) {
  248 + $where[$this->keyNameGroup] = $this->owner->getAttribute($this->keyNameGroup);
  249 + }
  250 +
  251 + return $this->owner->find()->where($where)->one();
190 252 }
191 253  
192 254 /*
... ... @@ -216,7 +278,11 @@ class ArtboxTreeBehavior extends Behavior {
216 278 * @return ActiveQuery
217 279 */
218 280 public function getChildrenAL() {
219   - return $this->owner->find()->where([$this->keyNameParentId => $this->owner->getAttribute($this->keyNameId), $this->keyNameGroup => $this->owner->getAttribute($this->keyNameGroup)]);
  281 + $where = [$this->keyNameParentId => $this->owner->getAttribute($this->keyNameId)];
  282 + if ($this->keyNameGroup) {
  283 + $where[$this->keyNameGroup] = $this->owner->getAttribute($this->keyNameGroup);
  284 + }
  285 + return $this->owner->find()->where($where);
220 286 }
221 287  
222 288 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... ... @@ -271,7 +337,7 @@ class ArtboxTreeBehavior extends Behavior {
271 337 */
272 338 protected static function getParentPathInternal($path, $asArray = false)
273 339 {
274   - $path = str_replace(['{', '}'], '', explode(',', $path));
  340 + $path = explode(',', trim($path, '{}'));
275 341 array_pop($path);
276 342 if ($asArray) {
277 343 return $path;
... ... @@ -348,7 +414,7 @@ class ArtboxTreeBehavior extends Behavior {
348 414 // $this->keyNamePath => $path
349 415 // ]);
350 416  
351   - $this->owner->setAttribute('path_int', $path);
  417 + $this->owner->setAttribute($this->keyNamePath, $path);
352 418 // $this->owner->setAttribute($this->keyNamePath, $path);
353 419 $this->owner->setAttribute($this->keyNameDepth, $depth);
354 420 }
... ...
common/components/artboxtree/ArtboxTreeQueryTrait.php
... ... @@ -23,18 +23,17 @@ trait ArtboxTreeQueryTrait {
23 23 return self::$model;
24 24 }
25 25  
26   - public function getTree($group, $cached = true) {
27   - if ($cached && isset(self::$cache_tree[$group]))
28   - return self::$cache_tree[$group];
29   -
  26 + public function getTree($group = null) {
30 27 $model = $this->getModel();
31   - $data = $this->andWhere([$model->keyNameGroup => $group])->all();
  28 + if ($group !== null) {
  29 + $data = $this->andWhere([$model->keyNameGroup => $group])->all();
  30 + } else {
  31 + $data = $this->all();
  32 + }
32 33 if (empty($data))
33 34 return [];
34 35  
35   - self::$cache_tree[$group] = $this->buildTree($data);
36   -
37   - return self::$cache_tree[$group];
  36 + return $this->buildTree($data);
38 37 }
39 38  
40 39 private function _recursiveRebuild($tree, $parentPath = null, $depth = 0) {
... ...
common/config/main.php
... ... @@ -86,16 +86,13 @@ return [
86 86 'linked_key' => 'product_id',
87 87 ],
88 88 'entity2' => [
89   - 'model' => '\common\modules\rubrication\models\TaxOption',
  89 + 'model' => '\common\modules\product\models\Category',
90 90 'label' => 'Category',
91   - 'listField' => 'ValueRenderFlash',
92   - 'key' => 'tax_option_id',
  91 + 'listField' => 'name',
  92 + 'key' => 'category_id',
93 93 'linked_key' => 'category_id',
94   - 'where' => [
95   - 'tax_group_id' => 1
96   - ],
97 94 'hierarchy' => [
98   - 'key' => 'tax_option_id',
  95 + 'key' => 'category_id',
99 96 'parentKey' => 'parent_id',
100 97 ]
101 98 ],
... ... @@ -103,6 +100,33 @@ return [
103 100 'model' => '\common\modules\product\models\ProductCategory',
104 101 ]
105 102 ],
  103 + 'tax_group_to_category' => [
  104 + 'name' => Yii::t('product', 'Характеристики по категориям'),
  105 + 'field' => 'option_to_category',
  106 + 'entity1' => [
  107 + 'model' => '\common\modules\rubrication\models\TaxGroup',
  108 + 'label' => 'Group',
  109 + 'listField' => 'name',
  110 + 'key' => 'tax_group_id',
  111 + 'linked_key' => 'entity1_id',
  112 + ],
  113 + 'entity2' => [
  114 + 'model' => '\common\modules\product\models\Category',
  115 + 'label' => 'Category',
  116 + 'listField' => 'name',
  117 + 'key' => 'category_id',
  118 + 'linked_key' => 'entity2_id',
  119 + 'hierarchy' => [
  120 + 'key' => 'category_id',
  121 + 'parentKey' => 'parent_id',
  122 + ]
  123 + ],
  124 + 'via' => [
  125 + 'model' => '\common\modules\relation\models\Relation',
  126 + 'alias' => 'alias',
  127 + ]
  128 + ],
  129 + /*
106 130 'relation_categories' => [
107 131 'name' => Yii::t('relation', 'Relation categories'),
108 132 'field' => 'categories',
... ... @@ -194,7 +218,7 @@ return [
194 218 'model' => 'common\modules\rubrication\models\TaxOptionRelation',
195 219 'alias' => 'alias',
196 220 ]
197   - ]
  221 + ]*/
198 222 ]
199 223 ],
200 224 'comment' => [
... ...
common/modules/product/controllers/ManageController.php
... ... @@ -2,6 +2,9 @@
2 2  
3 3 namespace common\modules\product\controllers;
4 4  
  5 +use common\modules\product\helpers\ProductHelper;
  6 +use common\modules\product\models\Category;
  7 +use common\modules\product\models\ProductVariant;
5 8 use Yii;
6 9 use common\modules\product\models\Product;
7 10 use common\modules\product\models\ProductSearch;
... ... @@ -29,6 +32,38 @@ class ManageController extends Controller
29 32 ];
30 33 }
31 34  
  35 + public function actionTest() {
  36 + $categories = Category::find()->where(['depth' => 2])->all();
  37 + $cats_ids = [];
  38 + foreach($categories as $cat) {
  39 + $cats_ids[] = $cat->category_id;
  40 + }
  41 +
  42 + $brands = ProductHelper::getBrands()->all();
  43 + $brands_ids = [];
  44 + foreach($brands as $brand) {
  45 + $brands_ids[] = $brand->brand_id;
  46 + }
  47 +
  48 + for($i=1;$i<=1000;$i++) {
  49 + $uniqid = uniqid();
  50 + $model = new Product();
  51 + $model->name = 'Test '. $uniqid;
  52 + $model->brand_id = $brands_ids[array_rand($brands_ids, 1)];
  53 + $model->categories = [$cats_ids[array_rand($cats_ids, 1)]];
  54 + $model->save();
  55 +
  56 + $variantModel = new ProductVariant();
  57 + $variantModel->product_id = $model->product_id;
  58 + $variantModel->name = 'test-'. $uniqid;
  59 + $variantModel->sku = $variantModel->name;
  60 + $variantModel->price = rand(5, 200000);
  61 + $variantModel->price_old = rand(0, 5) > 3 ? $variantModel->price* (1+rand(0, 10) / 10) : $variantModel->price;
  62 + $variantModel->product_unit_id = rand(1, 5);
  63 + $variantModel->save();
  64 + }
  65 + }
  66 +
32 67 /**
33 68 * Lists all Product models.
34 69 * @return mixed
... ...
common/modules/product/helpers/ProductHelper.php
... ... @@ -2,23 +2,16 @@
2 2  
3 3 namespace common\modules\product\helpers;
4 4  
5   -use common\modules\rubrication\models\TaxOption;
  5 +use common\modules\product\models\Brand;
  6 +use common\modules\product\models\Category;
6 7 use yii\base\Object;
7 8  
8 9 class ProductHelper extends Object {
9   - public static function getCategoryGroupId() {
10   - return \Yii::$app->getModule('product')->params['category_group'];
11   - }
12   -
13   - public static function getBrandGroupId() {
14   - return \Yii::$app->getModule('product')->params['brand_group'];
15   - }
16   -
17 10 public static function getCategories() {
18   - return TaxOption::find()->getTree(self::getCategoryGroupId());
  11 + return Category::find()->getTree();
19 12 }
20 13  
21 14 public static function getBrands() {
22   - return TaxOption::find()->where(['tax_group_id' => self::getBrandGroupId()]);
  15 + return Brand::find();
23 16 }
24 17 }
25 18 \ No newline at end of file
... ...
common/modules/product/models/Category.php
... ... @@ -4,6 +4,7 @@ namespace common\modules\product\models;
4 4  
5 5 use common\behaviors\Slug;
6 6 use common\components\artboxtree\ArtboxTreeBehavior;
  7 +use common\modules\relation\relationBehavior;
7 8 use common\modules\rubrication\behaviors\ArtboxSynonymBehavior;
8 9 use Yii;
9 10  
... ... @@ -53,6 +54,12 @@ class Category extends \yii\db\ActiveRecord
53 54 'valueFields' => [ // postKey => DBFieldName
54 55 'name' => 'value'
55 56 ]
  57 + ],
  58 + [
  59 + 'class' => relationBehavior::className(),
  60 + 'relations' => [
  61 + 'product_categories' => 'entity2' // Products of category
  62 + ]
56 63 ]
57 64 ];
58 65 }
... ...
common/modules/product/models/Product.php
... ... @@ -11,13 +11,18 @@ use yii\db\ActiveQuery;
11 11 * This is the model class for table "{{%product}}".
12 12 *
13 13 * @property string $name
14   - * @property integer $tax_brand_id
  14 + * @property integer $brand_id
15 15 * @property integer $product_id
16   - *
17   - * @property TaxOption $categories
  16 + * @property Category $category
  17 + * @property array $categories
  18 + * @property ProductVariant $variant
  19 + * @property ProductImage $image
  20 + * @property array $images
18 21 */
19 22 class Product extends \yii\db\ActiveRecord
20 23 {
  24 + /** @var array $variants */
  25 + public $_variants = [];
21 26 /**
22 27 * @inheritdoc
23 28 */
... ... @@ -47,9 +52,9 @@ class Product extends \yii\db\ActiveRecord
47 52 public function rules()
48 53 {
49 54 return [
50   - [['tax_brand_id'], 'integer'],
  55 + [['brand_id'], 'integer'],
51 56 [['name'], 'string', 'max' => 150],
52   - [['categories'], 'safe'],
  57 + [['categories', 'variants'], 'safe'],
53 58 // [['product_id'], 'exist', 'skipOnError' => true, 'targetClass' => Product::className(), 'targetAttribute' => ['product_id' => 'product_id']],
54 59 ];
55 60 }
... ... @@ -60,11 +65,11 @@ class Product extends \yii\db\ActiveRecord
60 65 public function attributeLabels()
61 66 {
62 67 return [
63   - 'product_id' => Yii::t('product', 'Product ID'),
  68 + 'product_id' => Yii::t('product', 'ID'),
64 69 'name' => Yii::t('product', 'Name'),
65   - 'tax_brand_id' => Yii::t('product', 'Brand'),
66   - 'brand' => Yii::t('product', 'Brand'),
  70 + 'brand_id' => Yii::t('product', 'Brand'),
67 71 'categories' => Yii::t('product', 'Categories'), // relation behavior field
  72 + 'category' => Yii::t('product', 'Category'), // relation behavior field
68 73 ];
69 74 }
70 75  
... ... @@ -73,17 +78,63 @@ class Product extends \yii\db\ActiveRecord
73 78 */
74 79 public function getBrand()
75 80 {
76   - return $this->hasOne(TaxOption::className(), ['tax_option_id' => 'tax_brand_id']);
  81 + return $this->hasOne(Brand::className(), ['brand_id' => 'brand_id']);
77 82 }
78 83  
79   - public function getFullName()
  84 + /**
  85 + * @return \yii\db\ActiveQuery
  86 + */
  87 + public function getImage()
  88 + {
  89 + return $this->hasOne(ProductImage::className(), ['product_id' => 'product_id']);
  90 + }
  91 +
  92 + /**
  93 + * @return \yii\db\ActiveQuery
  94 + */
  95 + public function getImages()
80 96 {
81   - return $this->brandname .' '. $this->name;
  97 + return $this->hasMany(ProductImage::className(), ['product_id' => 'product_id']);
82 98 }
83 99  
84   - public function getBrandName()
  100 + /**
  101 + * @return \yii\db\ActiveQuery
  102 + */
  103 + public function getVariant()
85 104 {
86   - return $this->getBrand()->one()->valueRenderHTML;
  105 + return $this->hasOne(ProductVariant::className(), ['product_id' => 'product_id']);
  106 + }
  107 +
  108 + public function getVariantPrice() {
  109 + return $this->variant->price;
  110 + }
  111 + /**
  112 + * @return \yii\db\ActiveQuery
  113 + */
  114 + public function getVariants()
  115 + {
  116 + return $this->hasMany(ProductVariant::className(), ['product_id' => 'product_id']);
  117 + }
  118 +
  119 + public function setVariants($variants) {
  120 + $this->_variants = $variants;
  121 + }
  122 +
  123 + public function getFullName()
  124 + {
  125 + return $this->brand->name .' '. $this->name;
  126 + }
  127 +
  128 + public function getCategories() {
  129 + return $this->getRelations('product_categories');
  130 + }
  131 +
  132 + public function getCategoriesNames() {
  133 + $result = [];
  134 + foreach($this->categories as $category) {
  135 + $result[] = $category->name;
  136 + }
  137 + return $result;
87 138 }
88 139  
89 140 public function getCategory() {
... ... @@ -91,8 +142,8 @@ class Product extends \yii\db\ActiveRecord
91 142 $categories = $this->getRelations('product_categories');
92 143 $count = $categories->count();
93 144 if ($count == 0)
94   - return 'None';
95   - return $categories->one()->ValueRenderFlash . ($count > 1 ? ' + '. $count : '');
  145 + return;
  146 + return $categories->one();
96 147 }
97 148  
98 149 /**
... ... @@ -103,4 +154,28 @@ class Product extends \yii\db\ActiveRecord
103 154 {
104 155 return new ProductQuery(get_called_class());
105 156 }
  157 +
  158 + public function afterSave($insert, $changedAttributes)
  159 + {
  160 + parent::afterSave($insert, $changedAttributes);
  161 +
  162 + $todel = [];
  163 + foreach ($this->variants ? : [] as $_variant) {
  164 + $todel[$_variant->product_variant_id] = $_variant->product_variant_id;
  165 + }
  166 + foreach ($this->_variants as $_variant) {
  167 + if (!empty($_variant['product_variant_id'])) {
  168 + unset($todel[$_variant['product_variant_id']]);
  169 + $model = ProductVariant::findOne($_variant['product_variant_id']);
  170 + } else {
  171 + $model = new ProductVariant();
  172 + }
  173 + $_variant['product_id'] = $this->product_id;
  174 + $model->load(['ProductVariant' => $_variant]);
  175 + $model->save();
  176 + }
  177 + if (!empty($todel)) {
  178 + ProductVariant::deleteAll(['product_variant_id' => $todel]);
  179 + }
  180 + }
106 181 }
... ...
common/modules/product/models/ProductCategory.php
... ... @@ -26,15 +26,14 @@ class ProductCategory extends Relation
26 26 return 'product_category';
27 27 }
28 28  
29   -
30 29 /**
31 30 * @inheritdoc
32 31 */
33 32 public function rules()
34 33 {
35 34 return [
36   - [['product_id', 'dev_category_id'], 'integer'],
37   - [['dev_category_id'], 'exist', 'skipOnError' => true, 'targetClass' => TaxOption::className(), 'targetAttribute' => ['dev_category_id' => 'tax_option_id']],
  35 + [['product_id', 'category_id'], 'integer'],
  36 + [['category_id'], 'exist', 'skipOnError' => true, 'targetClass' => Category::className(), 'targetAttribute' => ['category_id' => 'category_id']],
38 37 [['product_id'], 'exist', 'skipOnError' => true, 'targetClass' => Product::className(), 'targetAttribute' => ['product_id' => 'product_id']],
39 38 ];
40 39 }
... ... @@ -46,7 +45,7 @@ class ProductCategory extends Relation
46 45 {
47 46 return [
48 47 'product_id' => Yii::t('product', 'Product'),
49   - 'dev_category_id' => Yii::t('product', 'Category'),
  48 + 'category_id' => Yii::t('product', 'Category'),
50 49 ];
51 50 }
52 51  
... ...
common/modules/product/models/ProductImage.php 0 → 100644
  1 +<?php
  2 +
  3 +namespace common\modules\product\models;
  4 +
  5 +use Yii;
  6 +use yii\web\UploadedFile;
  7 +
  8 +/**
  9 + * This is the model class for table "product_image".
  10 + *
  11 + * @property integer $product_image_id
  12 + * @property integer $product_id
  13 + * @property string $image
  14 + * @property string $alt
  15 + * @property string $title
  16 + *
  17 + * @property Product $product
  18 + */
  19 +class ProductImage extends \yii\db\ActiveRecord
  20 +{
  21 + public $image;
  22 + /**
  23 + * @inheritdoc
  24 + */
  25 + public static function tableName()
  26 + {
  27 + return 'product_image';
  28 + }
  29 +
  30 + /**
  31 + * @inheritdoc
  32 + */
  33 + public function rules()
  34 + {
  35 + return [
  36 + [['product_image_id', 'product_id'], 'required'],
  37 + [['product_image_id', 'product_id'], 'integer'],
  38 + [['alt', 'title'], 'string', 'max' => 255],
  39 + [['image'], 'safe'],
  40 + [['product_id'], 'exist', 'skipOnError' => true, 'targetClass' => Product::className(), 'targetAttribute' => ['product_id' => 'product_id']],
  41 + [['image'], 'file', 'extensions'=>'jpg, gif, png'],
  42 + ];
  43 + }
  44 +
  45 + /**
  46 + * @inheritdoc
  47 + */
  48 + public function attributeLabels()
  49 + {
  50 + return [
  51 + 'product_image_id' => Yii::t('product', 'Product Image ID'),
  52 + 'product_id' => Yii::t('product', 'Product ID'),
  53 + 'image' => Yii::t('product', 'Image'),
  54 + 'alt' => Yii::t('product', 'Alt'),
  55 + 'title' => Yii::t('product', 'Title'),
  56 + ];
  57 + }
  58 +
  59 + /**
  60 + * @return \yii\db\ActiveQuery
  61 + */
  62 + public function getProduct()
  63 + {
  64 + return $this->hasOne(Product::className(), ['product_id' => 'product_id']);
  65 + }
  66 +
  67 + /**
  68 + * @inheritdoc
  69 + * @return ProductImageQuery the active query used by this AR class.
  70 + */
  71 + public static function find()
  72 + {
  73 + return new ProductImageQuery(get_called_class());
  74 + }
  75 +
  76 + /**
  77 + * fetch stored image file name with complete path
  78 + * @return string
  79 + */
  80 + public function getImageFile()
  81 + {
  82 + return isset($this->image) ? Yii::$app->params['uploadPath'] . $this->image : null;
  83 + }
  84 +
  85 + /**
  86 + * fetch stored image url
  87 + * @return string
  88 + */
  89 + public function getImageUrl()
  90 + {
  91 + // return a default image placeholder if your source image is not found
  92 + $image = isset($this->image) ? $this->image : 'default.jpg';
  93 + return Yii::$app->params['uploadUrl'] . $image;
  94 + }
  95 +
  96 + /**
  97 + * Process upload of image
  98 + *
  99 + * @return mixed the uploaded image instance
  100 + */
  101 + public function uploadImage() {
  102 + // get the uploaded file instance. for multiple file uploads
  103 + // the following data will return an array (you may need to use
  104 + // getInstances method)
  105 + $image = UploadedFile::getInstance($this, 'image');
  106 +
  107 + // if no image was uploaded abort the upload
  108 + if (empty($image)) {
  109 + return false;
  110 + }
  111 +
  112 + // store the source file name
  113 + $this->filename = $image->name;
  114 + $ext = end((explode(".", $image->name)));
  115 +
  116 + // generate a unique file name
  117 + $this->image = Yii::$app->security->generateRandomString().".{$ext}";
  118 +
  119 + // the uploaded image instance
  120 + return $image;
  121 + }
  122 +
  123 + /**
  124 + * Process deletion of image
  125 + *
  126 + * @return boolean the status of deletion
  127 + */
  128 + public function deleteImage() {
  129 + $file = $this->getImageFile();
  130 +
  131 + // check if file exists on server
  132 + if (empty($file) || !file_exists($file)) {
  133 + return false;
  134 + }
  135 +
  136 + // check if uploaded file can be deleted on server
  137 + if (!unlink($file)) {
  138 + return false;
  139 + }
  140 +
  141 + // if deletion successful, reset your file attributes
  142 + $this->image = null;
  143 + $this->filename = null;
  144 +
  145 + return true;
  146 + }
  147 +}
... ...
common/modules/product/models/ProductImageQuery.php 0 → 100644
  1 +<?php
  2 +
  3 +namespace common\modules\product\models;
  4 +
  5 +/**
  6 + * This is the ActiveQuery class for [[ProductImage]].
  7 + *
  8 + * @see ProductImage
  9 + */
  10 +class ProductImageQuery extends \yii\db\ActiveQuery
  11 +{
  12 + /*public function active()
  13 + {
  14 + return $this->andWhere('[[status]]=1');
  15 + }*/
  16 +
  17 + /**
  18 + * @inheritdoc
  19 + * @return ProductImage[]|array
  20 + */
  21 + public function all($db = null)
  22 + {
  23 + return parent::all($db);
  24 + }
  25 +
  26 + /**
  27 + * @inheritdoc
  28 + * @return ProductImage|array|null
  29 + */
  30 + public function one($db = null)
  31 + {
  32 + return parent::one($db);
  33 + }
  34 +}
... ...
common/modules/product/models/ProductQuery.php
... ... @@ -31,4 +31,15 @@ class ProductQuery extends \yii\db\ActiveQuery
31 31 {
32 32 return parent::one($db);
33 33 }
  34 +
  35 + /**
  36 + * Select category by alias
  37 + * @param $slug
  38 + * @return $this
  39 + */
  40 + public function byAlias($alias)
  41 + {
  42 + $this->andFilterWhere(['alias' => $alias]);
  43 + return $this;
  44 + }
34 45 }
... ...
common/modules/product/models/ProductSearch.php
... ... @@ -67,4 +67,16 @@ class ProductSearch extends Product
67 67  
68 68 return $dataProvider;
69 69 }
  70 +
  71 + public static function findByAlias($alias) {
  72 + /** @var ProductQuery $query */
  73 + $query = Category::find();
  74 + $query->byAlias($alias);
  75 + if (($model = $query->one()) !== null) {
  76 + return $model;
  77 + } else {
  78 + throw new NotFoundHttpException('The requested product does not exist.');
  79 + }
  80 + }
  81 +
70 82 }
... ...
common/modules/product/models/ProductVariant.php 0 → 100644
  1 +<?php
  2 +
  3 +namespace common\modules\product\models;
  4 +
  5 +use Yii;
  6 +
  7 +/**
  8 + * This is the model class for table "product_variant".
  9 + *
  10 + * @property integer $product_variant_id
  11 + * @property integer $product_id
  12 + * @property string $name
  13 + * @property string $sku
  14 + * @property double $price
  15 + * @property double $price_old
  16 + * @property double $stock
  17 + * @property integer $product_unit_id
  18 + *
  19 + * @property ProductUnit $productUnit
  20 + */
  21 +class ProductVariant extends \yii\db\ActiveRecord
  22 +{
  23 + /**
  24 + * @inheritdoc
  25 + */
  26 + public static function tableName()
  27 + {
  28 + return 'product_variant';
  29 + }
  30 +
  31 + /**
  32 + * @inheritdoc
  33 + */
  34 + public function rules()
  35 + {
  36 + return [
  37 + [['product_id', 'name', 'sku', 'product_unit_id'], 'required'],
  38 + [['product_id', 'product_unit_id'], 'integer'],
  39 + [['price', 'price_old', 'stock'], 'number'],
  40 + [['name', 'sku'], 'string', 'max' => 255],
  41 + [['product_unit_id'], 'exist', 'skipOnError' => true, 'targetClass' => ProductUnit::className(), 'targetAttribute' => ['product_unit_id' => 'product_unit_id']],
  42 + ];
  43 + }
  44 +
  45 + /**
  46 + * @inheritdoc
  47 + */
  48 + public function attributeLabels()
  49 + {
  50 + return [
  51 + 'product_variant_id' => Yii::t('product', 'Product Variant ID'),
  52 + 'product_id' => Yii::t('product', 'Product ID'),
  53 + 'name' => Yii::t('product', 'Name'),
  54 + 'sku' => Yii::t('product', 'Sku'),
  55 + 'price' => Yii::t('product', 'Price'),
  56 + 'price_old' => Yii::t('product', 'Price Old'),
  57 + 'stock' => Yii::t('product', 'Stock'),
  58 + 'product_unit_id' => Yii::t('product', 'Product Unit ID'),
  59 + ];
  60 + }
  61 +
  62 + /**
  63 + * @return \yii\db\ActiveQuery
  64 + */
  65 + public function getProductUnit()
  66 + {
  67 + return $this->hasOne(ProductUnit::className(), ['product_unit_id' => 'product_unit_id']);
  68 + }
  69 +
  70 + /**
  71 + * @inheritdoc
  72 + * @return ProductVariantQuery the active query used by this AR class.
  73 + */
  74 + public static function find()
  75 + {
  76 + return new ProductVariantQuery(get_called_class());
  77 + }
  78 +}
... ...
common/modules/product/models/ProductVariantQuery.php 0 → 100644
  1 +<?php
  2 +
  3 +namespace common\modules\product\models;
  4 +
  5 +/**
  6 + * This is the ActiveQuery class for [[ProductVariant]].
  7 + *
  8 + * @see ProductVariant
  9 + */
  10 +class ProductVariantQuery extends \yii\db\ActiveQuery
  11 +{
  12 + /*public function active()
  13 + {
  14 + return $this->andWhere('[[status]]=1');
  15 + }*/
  16 +
  17 + /**
  18 + * @inheritdoc
  19 + * @return ProductVariant[]|array
  20 + */
  21 + public function all($db = null)
  22 + {
  23 + return parent::all($db);
  24 + }
  25 +
  26 + /**
  27 + * @inheritdoc
  28 + * @return ProductVariant|array|null
  29 + */
  30 + public function one($db = null)
  31 + {
  32 + return parent::one($db);
  33 + }
  34 +}
... ...
common/modules/product/views/manage/_form.php
... ... @@ -5,6 +5,9 @@ use yii\widgets\ActiveForm;
5 5 use yii\helpers\ArrayHelper;
6 6 use common\components\artboxtree\ArtboxTreeHelper;
7 7 use common\modules\product\helpers\ProductHelper;
  8 +use kartik\file\FileInput;
  9 +use unclead\widgets\MultipleInput;
  10 +use unclead\widgets\MultipleInputColumn;
8 11  
9 12 /* @var $this yii\web\View */
10 13 /* @var $model common\modules\product\models\Product */
... ... @@ -17,23 +20,76 @@ use common\modules\product\helpers\ProductHelper;
17 20  
18 21 <?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
19 22  
20   - <?= $form->field($model, 'tax_brand_id')->dropDownList(
21   - ArrayHelper::map(ProductHelper::getBrands()->all(), 'tax_option_id', 'ValueRenderFlash'),
  23 + <?= $form->field($model, 'brand_id')->dropDownList(
  24 + ArrayHelper::map(ProductHelper::getBrands()->all(), 'brand_id', 'name'),
22 25 [
23 26 'prompt' => Yii::t('product', 'Select brand')
24 27 ]
25 28 ) ?>
26   - <?php
27   -// var_dump($model->categories);
28   - ?>
  29 +
29 30 <?= $form->field($model, 'categories')->dropDownList(
30   - ArtboxTreeHelper::treeMap(ProductHelper::getCategories(), 'tax_option_id', 'ValueRenderFlash'),
  31 + ArtboxTreeHelper::treeMap(ProductHelper::getCategories(), 'category_id', 'name'),
31 32 [
32 33 // 'prompt' => Yii::t('product', 'Select category'),
33 34 'multiple' => true
34 35 ]
35 36 ) ?>
36 37  
  38 + <?php /*= $form->field($model, 'images[]')->widget(FileInput::classname(), [
  39 + 'options' => [
  40 + 'accept' => 'image/*',
  41 + 'multiple' => true,
  42 + ],
  43 + 'pluginOptions' => [
  44 +// 'uploadUrl' => \yii\helpers\Url::to(['/site/file-upload']),
  45 + ]
  46 + ]);
  47 + */?>
  48 +
  49 + <?= $form->field($model, 'variants')->widget(MultipleInput::className(), [
  50 + 'columns' => [
  51 + [
  52 + 'name' => 'product_variant_id',
  53 + 'type' => MultipleInputColumn::TYPE_HIDDEN_INPUT,
  54 + ],
  55 + [
  56 + 'name' => 'name',
  57 + 'type' => MultipleInputColumn::TYPE_TEXT_INPUT,
  58 + 'title' => 'Name',
  59 + ],
  60 + [
  61 + 'name' => 'sku',
  62 + 'type' => MultipleInputColumn::TYPE_TEXT_INPUT,
  63 + 'title' => 'SKU',
  64 + ],
  65 + [
  66 + 'name' => 'price',
  67 + 'type' => MultipleInputColumn::TYPE_TEXT_INPUT,
  68 + 'title' => 'Price',
  69 + ],
  70 + [
  71 + 'name' => 'price_old',
  72 + 'type' => MultipleInputColumn::TYPE_TEXT_INPUT,
  73 + 'title' => 'Old Price',
  74 + ],
  75 + [
  76 + 'name' => 'product_unit_id',
  77 + 'type' => MultipleInputColumn::TYPE_DROPDOWN,
  78 + 'title' => 'Unit',
  79 + 'items' => ArrayHelper::map(\common\modules\product\models\ProductUnit::find()->all(), 'product_unit_id', 'name'),
  80 + ],
  81 + [
  82 + 'name' => 'stock',
  83 + 'type' => MultipleInputColumn::TYPE_TEXT_INPUT,
  84 + 'title' => 'Stock',
  85 + 'options' => [
  86 + 'placeholder' => '∞'
  87 + ],
  88 + ],
  89 + ],
  90 + ]);
  91 + ?>
  92 +
37 93 <div class="form-group">
38 94 <?= Html::submitButton($model->isNewRecord ? Yii::t('product', 'Create') : Yii::t('product', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
39 95 </div>
... ...
common/modules/product/views/manage/_search.php
... ... @@ -17,7 +17,7 @@ use yii\widgets\ActiveForm;
17 17  
18 18 <?= $form->field($model, 'name') ?>
19 19  
20   - <?= $form->field($model, 'tax_brand_id') ?>
  20 + <?= $form->field($model, 'brand_id') ?>
21 21  
22 22 <?= $form->field($model, 'product_id') ?>
23 23  
... ...
common/modules/product/views/manage/index.php
... ... @@ -22,11 +22,10 @@ $this-&gt;params[&#39;breadcrumbs&#39;][] = $this-&gt;title;
22 22 'filterModel' => $searchModel,
23 23 'columns' => [
24 24 ['class' => 'yii\grid\SerialColumn'],
25   -
26   -// 'product_id',
27   - 'fullname',
28   - 'brandname',
29   - 'category',
  25 + 'product_id',
  26 + 'name',
  27 + 'brand.name',
  28 + 'category.name',
30 29  
31 30 ['class' => 'yii\grid\ActionColumn'],
32 31 ],
... ...
common/modules/product/views/manage/view.php
... ... @@ -28,9 +28,11 @@ $this-&gt;params[&#39;breadcrumbs&#39;][] = $this-&gt;title;
28 28 <?= DetailView::widget([
29 29 'model' => $model,
30 30 'attributes' => [
31   - 'name',
32   - 'tax_brand_id',
33 31 'product_id',
  32 + 'name',
  33 + 'fullname',
  34 + 'brand.name',
  35 + 'category.name',
34 36 ],
35 37 ]) ?>
36 38  
... ...
common/modules/product/widgets/views/submenu.php
1 1 <div class="menu_item">
2   - <?= \yii\helpers\Html::a($rootCategory->name, ['product/category', 'alias' => $rootCategory->alias], ['class' => 'submenu_button '. $rootClass])?>
  2 + <?= \yii\helpers\Html::a($rootCategory->name, ['catalog/category', 'alias' => $rootCategory->alias], ['class' => 'submenu_button '. $rootClass])?>
3 3 <div class="submenu">
4 4 <ul class="categories">
5 5 <li class="sub_cat"><span>Популярные категории</span>
... ... @@ -7,7 +7,7 @@
7 7 <div class="sub_cat_content">
8 8 <div class="content_items">
9 9 <?php foreach($populary as $_item) :?>
10   - <div class="content_item"><a href="<?= \yii\helpers\Url::to(['product/category', 'alias' => $_item->alias])?>">
  10 + <div class="content_item"><a href="<?= \yii\helpers\Url::to(['catalog/category', 'alias' => $_item->alias])?>">
11 11 <div valign="top" class="picture"><img valign="top" src="<?= $_item->image?>"></div>
12 12 <div class="title"><?= $_item->name?></div>
13 13 </a></div>
... ... @@ -23,7 +23,7 @@
23 23 <div class="sub_cat_content">
24 24 <div class="content_items">
25 25 <?php foreach($item['children'] as $_item) :?>
26   - <div class="content_item"><a href="<?= \yii\helpers\Url::to(['/catalog/' .$_item['item']->alias])?>">
  26 + <div class="content_item"><a href="<?= \yii\helpers\Url::to(['/catalog/category', 'alias' => $_item['item']->alias])?>">
27 27 <div valign="top" class="picture"><img valign="top" src="<?= $_item['item']->image?>"></div>
28 28 <div class="title"><?= $_item['item']->name?></div>
29 29 </a></div>
... ...
common/modules/relation/relationQueryTrait.php
... ... @@ -21,7 +21,13 @@ trait relationQueryTrait {
21 21 return self::$model;
22 22 }
23 23  
24   - public function getRelations($relationKey) {
  24 + /*public function getRelations($relation) {
  25 + $model = $this->getModel();
25 26  
26   - }
  27 + $relation = $model->_getRelation($relation);
  28 + return
  29 + $model->owner
  30 + ->hasMany($relation['outer']['model'], [$relation['outer']['key'] => $relation['outer']['linked_key']])
  31 + ->viaTable($relation['linked_table'], [$relation['inner']['linked_key'] => $relation['inner']['key']]);
  32 + }*/
27 33 }
28 34 \ No newline at end of file
... ...
common/modules/rubrication/Module.php
... ... @@ -8,7 +8,7 @@ use Yii;
8 8 */
9 9 class Module extends \yii\base\Module
10 10 {
11   - public $types;
  11 + public $types = [];
12 12 /**
13 13 * @inheritdoc
14 14 */
... ... @@ -22,7 +22,5 @@ class Module extends \yii\base\Module
22 22 public function init()
23 23 {
24 24 parent::init();
25   -
26   - // custom initialization code goes here
27 25 }
28 26 }
... ...
common/modules/rubrication/models/TaxGroup.php
... ... @@ -35,6 +35,12 @@ class TaxGroup extends \yii\db\ActiveRecord
35 35 // 'tax_option_to_group' => 'entity2',
36 36 ]
37 37 ],
  38 + 'slug' => [
  39 + 'class' => 'common\behaviors\Slug',
  40 + 'in_attribute' => 'name',
  41 + 'out_attribute' => 'alias',
  42 + 'translit' => true
  43 + ],
38 44 ];
39 45 }
40 46  
... ... @@ -52,7 +58,7 @@ class TaxGroup extends \yii\db\ActiveRecord
52 58 public function rules()
53 59 {
54 60 return [
55   - [['alias', 'name', 'module'], 'required'],
  61 + [['name', 'module'], 'required'],
56 62 [['description', 'settings'], 'string'],
57 63 [['hierarchical'], 'boolean'],
58 64 [['alias', 'module'], 'string', 'max' => 50],
... ...
common/modules/rubrication/models/TaxOption.php
... ... @@ -53,13 +53,6 @@ class TaxOption extends \yii\db\ActiveRecord
53 53 ];
54 54 }
55 55  
56   - public function events() {
57   - return $this->events();
58   - return [
59   -// ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
60   - ];
61   - }
62   -
63 56 /**
64 57 * @inheritdoc
65 58 */
... ... @@ -226,6 +219,7 @@ class TaxOption extends \yii\db\ActiveRecord
226 219  
227 220 private function getValueModelName() {
228 221 $group = $this->getTaxGroup()->one();
  222 +// var_dump($group);exit;
229 223 $valueClass = '\common\modules\rubrication\models\TaxValue'. ucfirst($group->module);
230 224 return class_exists($valueClass) ? $valueClass : FALSE;
231 225 }
... ...
console/migrations/m160323_234304_product_image.php 0 → 100644
  1 +<?php
  2 +
  3 +use yii\db\Migration;
  4 +
  5 +class m160323_234304_product_image extends Migration
  6 +{
  7 + public function up()
  8 + {
  9 + $tableOptions = null;
  10 + if ($this->db->driverName === 'mysql') {
  11 + // Only for MySQL
  12 + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
  13 +
  14 + // @todo https://habrahabr.ru/post/138947/
  15 + } elseif ($this->db->driverName === 'pgsql') {
  16 + // Only for PostgreSQL
  17 + // @todo use intarray field for tax_options
  18 + }
  19 +
  20 + $this->createTable('{{%product_image}}', [
  21 + 'product_image_id' => $this->integer()->notNull(),
  22 + 'product_id' => $this->integer()->notNull(),
  23 + 'image' => $this->string(255),
  24 + 'alt' => $this->string(255),
  25 + 'title' => $this->string(255),
  26 + ], $tableOptions);
  27 + $this->addForeignKey('product_image_product_fkey', 'product_image', 'product_id', 'product', 'product_id', 'CASCADE', 'CASCADE');
  28 + }
  29 +
  30 + public function down()
  31 + {
  32 + $this->dropTable('{{%product_image}}');
  33 + }
  34 +
  35 + /*
  36 + // Use safeUp/safeDown to run migration code within a transaction
  37 + public function safeUp()
  38 + {
  39 + }
  40 +
  41 + public function safeDown()
  42 + {
  43 + }
  44 + */
  45 +}
... ...
frontend/controllers/CatalogController.php
... ... @@ -2,9 +2,17 @@
2 2  
3 3 namespace frontend\controllers;
4 4  
  5 +use common\modules\product\models\Brand;
5 6 use common\modules\product\models\Category;
6 7 use common\modules\product\models\CategorySearch;
7 8 use common\modules\product\models\Product;
  9 +use common\modules\product\models\ProductCategory;
  10 +use common\modules\product\models\ProductSearch;
  11 +use common\modules\product\models\ProductVariant;
  12 +use yii\data\ActiveDataProvider;
  13 +use yii\data\Pagination;
  14 +use yii\data\Sort;
  15 +use yii\db\ActiveQuery;
8 16 use yii\web\HttpException;
9 17  
10 18 class CatalogController extends \yii\web\Controller
... ... @@ -12,6 +20,9 @@ class CatalogController extends \yii\web\Controller
12 20 public function actionCategory($alias)
13 21 {
14 22 $category = CategorySearch::findByAlias($alias);
  23 + if (empty($category->category_id)) {
  24 + throw new HttpException(404 ,'Page not found');
  25 + }
15 26 if ($category->depth < 2) {
16 27 return $this->render(
17 28 'categories',
... ... @@ -20,11 +31,62 @@ class CatalogController extends \yii\web\Controller
20 31 ]
21 32 );
22 33 } else {
  34 +// $products = $category->getRelations('product_categories')->all();
  35 +
  36 + $per_page = 24;
  37 +
  38 + $sort = new Sort([
  39 + 'attributes' => [
  40 + 'name' => [
  41 + 'asc' => ['name' => SORT_ASC],
  42 + 'desc' => ['name' => SORT_DESC],
  43 + 'default' => SORT_DESC,
  44 + 'label' => 'имени',
  45 + ],
  46 + 'price' => [
  47 + 'asc' => [ProductVariant::tableName() .'.price' => SORT_ASC],
  48 + 'desc' => [ProductVariant::tableName() .'.price' => SORT_DESC],
  49 + 'default' => SORT_DESC,
  50 + 'label' => 'цене',
  51 + ],
  52 + ],
  53 + ]);
  54 + /** @var ActiveQuery $query */
  55 + $query = $category->getRelations('product_categories')
  56 + ->joinWith([
  57 + 'variants'
  58 + ]);
  59 + $pages = new Pagination(['totalCount' => $query->count(), 'pageSize' => $per_page]);
  60 + $count = $query->count();
  61 +
  62 +// $priceQuery = clone $query;
  63 + $priceMin = $query->min(ProductVariant::tableName() .'.price');
  64 + $priceMax = $query->max(ProductVariant::tableName() .'.price');
  65 +
  66 + $query->offset($pages->offset)
  67 + ->orderBy($sort->orders)
  68 + ->limit($pages->limit);
  69 + $products = $query->all();
  70 +
  71 + $brandsQuery = Brand::find()->innerJoinWith('products')->innerJoin(ProductCategory::tableName(), ProductCategory::tableName() .'.product_id='. Product::tableName() .'.product_id')->where([
  72 + ProductCategory::tableName() .'.category_id' => $category->category_id
  73 + ])->groupBy(Brand::tableName() .'.brand_id');
  74 + $brands = $brandsQuery->all();
  75 + $brands_count = $brandsQuery->count();
23 76  
24 77 return $this->render(
25 78 'products',
26 79 [
27 80 'category' => $category,
  81 + 'products' => $products,
  82 + 'product_count' => $count,
  83 + 'sort' => $sort,
  84 + 'pages' => $pages,
  85 + 'per_page' => $per_page,
  86 + 'priceMin' => $priceMin,
  87 + 'priceMax' => $priceMax,
  88 + 'brands' => $brands,
  89 + 'brands_count' => $brands_count,
28 90 ]
29 91 );
30 92 }
... ... @@ -32,10 +94,9 @@ class CatalogController extends \yii\web\Controller
32 94  
33 95 public function actionProduct($alias)
34 96 {
35   - $product = Product::find()->where('like', ['alias' => $alias]);
  97 + $product = ProductSearch::findByAlias($alias);
36 98 if (empty($product->product_id)) {
37   -// throw new HttpException(404 ,'Page not found');
38   -// return $this->redirect('/', 301);
  99 + throw new HttpException(404 ,'Page not found');
39 100 }
40 101 return $this->render('product');
41 102 }
... ...
frontend/views/catalog/product_item.php 0 → 100644
  1 +<?php
  2 +/** @var \common\modules\product\models\Product $product */
  3 +?>
  4 +<div class="item" data-id="<?= $product->product_id?>">
  5 + <!--<div class="new">АКЦИЯ</div>
  6 + <div class="top">Toп</div>-->
  7 + <a href="#" class="item_link"><div class="pic"><img src="/images/items/01.jpg"></div>
  8 + <div class="title_item"><?= $product->name?></div></a>
  9 + <div class="brand">Бренд: <span><?= $product->brand->name?></span></div>
  10 + <div class="type"><?= implode(', ', $product->categoriesNames)?></div>
  11 + <div class="price"><?= $product->variant->price?> <span>грн.</span></div>
  12 + <button class="basket_add_but" data-id="<?= $product->variant->product_variant_id?>">в корзину</button>
  13 + <a href="#" class="compare_add_but" data-id="<?= $product->product_id?>"><span>добавить к сравнению</span></a>
  14 + <img class="item_bottom_img" src="/images/nc_item_bottom.png" alt="">
  15 +</div>
0 16 \ No newline at end of file
... ...
frontend/views/catalog/products.php
... ... @@ -6,40 +6,434 @@ $this-&gt;title = $category-&gt;name;
6 6 foreach($category->getParents()->all() as $parent) {
7 7 $this->params['breadcrumbs'][] = ['label' => $parent->name, 'url' => ['catalog/category', 'alias' => $parent->alias]];
8 8 }
9   -$this->params['breadcrumbs'][] = $this->title;
  9 +$this->params['breadcrumbs'][] = $category->name;
10 10 ?>
11   -<h1 class="category_page_main_title"><?= $this->title ?></h1>
12   -
13   -<div class="category_wrap">
14   -
15   - <div class="category_wrap_3_colum"><!-- 3 colons for items ================================== 1rst colum -->
16   -
17   - <?php foreach($category->getAllChildrenTree(2) as $category) :?>
18   - <div class="wrap">
19   - <div class="cat_li_cont">
20   - <?php if (!empty($category['item']->image)) :?>
21   - <img src="<?= $category['item']->image?>" alt="<?= $category['item']->name?>">
22   - <?php else :?>
23   - <img src="/images/category/1.png" alt="">
24   - <?php endif;?>
25   - <div class="desc"><?= $category['item']->name?><!-- (133)--></div>
26   - <?php if(!empty($category['children'])) :?>
27   - <span class="arrow"></span>
28   - <?php endif?>
29   - </div>
30   - <?php if(!empty($category['children'])) :?>
31   - <div class="cat_li_sub_ul">
32   - <ul>
33   - <?php foreach($category['children'] as $_category) :?>
34   - <li><a href="<?= \yii\helpers\Url::to(['catalog/category', 'alias' => $_category['item']->alias])?>"><?= $_category['item']->name?><!-- (18)--></a></li>
35   - <?php endforeach?>
36   - </ul>
  11 +<script type="text/javascript">
  12 + $(document).ready(function() {
  13 + // price rangeslider (filter price slider)
  14 + $("#price_interval").ionRangeSlider({
  15 + type: "double",
  16 + min: <?= $priceMin?>,
  17 + max: <?= $priceMax?>,
  18 + from: <?= $priceMin?>,
  19 + to: <?= $priceMax?>,
  20 + grid: false
  21 + });
  22 + });
  23 +</script>
  24 +<div class="w_960">
  25 + <!-- side bar with all filters -->
  26 + <div class="cat_p_filter_bar">
  27 + <div class="title">ФИЛЬТРЫ</div>
  28 + <div class="filter_list">
  29 + <form action="#" name="filter_catalog_page_form">
  30 + <ul>
  31 + <li>Цена:
  32 + <div class="arrow"><img src="/images/head_up.png" alt=""></i></div>
  33 + <div class="price_filter first_price_li">
  34 + <div class="price_slider">
  35 + <input type="text" id="price_interval" name="price_interval" value="" />
  36 + </div>
  37 + <!--<div class="checkbox">
  38 + <label><input type="checkbox" name="venecia" value="0" /></label>
  39 + <a href="#">Акции</a>
  40 + </div>
  41 + <div class="checkbox">
  42 + <label><input type="checkbox" name="venecia" value="0" /></label>
  43 + <a href="#">Товар в наличии</a>
  44 + </div>
  45 + <div class="checkbox">
  46 + <label><input type="checkbox" name="venecia" value="0" /></label>
  47 + <a href="#">Хит продаж</a>
  48 + </div>-->
  49 + </div>
  50 + </li>
  51 +
  52 + <?php if ($brands_count) :?>
  53 + <li>Бренд
  54 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  55 + <div class="price_filter">
  56 + <?php foreach($brands as $brand) :?>
  57 + <div class="checkbox">
  58 + <label><input type="checkbox" name="think" value="ruuki" /></label>
  59 + <a href="<?= \yii\helpers\Url::to(['catalog/brand', 'alias' => $brand->alias])?>"><?= $brand->name?> (<?= $brand->getProducts()->count()?>)</a>
  60 + </div>
  61 + <?php endforeach?>
  62 + <!--<div class="checkbox see_all">
  63 + <i class="fa fa-plus-circle"></i>
  64 + <a href="#">посмотреть все</a>
  65 + </div>-->
  66 + </div>
  67 + </li>
  68 + <?php endif?>
  69 +
  70 + <div class="title_2">ПОДБОР ПО ПАРАМЕТРАМ</div>
  71 +
  72 + <li>Толщина металла, мм
  73 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  74 + <div class="price_filter">
  75 + <div class="checkbox">
  76 + <label><input type="checkbox" name="venecia" value="0" /></label>
  77 + <a href="#">0,4</a>
  78 + </div>
  79 + <div class="checkbox">
  80 + <label><input type="checkbox" name="venecia" value="0" /></label>
  81 + <a href="#">0,45</a>
  82 + </div>
  83 + <div class="checkbox">
  84 + <label><input type="checkbox" name="venecia" value="0" /></label>
  85 + <a href="#">0,5</a>
  86 + </div>
  87 + </div>
  88 + </li>
  89 +
  90 + <li>Покрытие
  91 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  92 + <div class="price_filter">
  93 + <div class="checkbox">
  94 + <label><input type="checkbox" name="venecia" value="0" /></label>
  95 + <a href="#">RUUKI(3)</a>
  96 + </div>
  97 + <div class="checkbox">
  98 + <label><input type="checkbox" name="venecia" value="0" /></label>
  99 + <a href="#">Venecia(10)</a>
  100 + </div>
  101 + </div>
  102 + </li>
  103 +
  104 + <li>Металл
  105 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  106 + <div class="price_filter">
  107 + <div class="checkbox">
  108 + <label><input type="checkbox" name="venecia" value="0" /></label>
  109 + <a href="#">RUUKI(3)</a>
  110 + </div>
  111 + <div class="checkbox">
  112 + <label><input type="checkbox" name="venecia" value="0" /></label>
  113 + <a href="#">Venecia(10)</a>
  114 + </div>
  115 + </div>
  116 + </li>
  117 +
  118 + <li>Производитель
  119 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  120 + <div class="price_filter">
  121 + <div class="checkbox">
  122 + <label><input type="checkbox" name="venecia" value="0" /></label>
  123 + <a href="#">RUUKI(3)</a>
  124 + </div>
  125 + <div class="checkbox">
  126 + <label><input type="checkbox" name="venecia" value="0" /></label>
  127 + <a href="#">Venecia(10)</a>
  128 + </div>
  129 + </div>
  130 + </li>
  131 +
  132 + <li>Профиль
  133 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  134 + <div class="price_filter">
  135 + <div class="checkbox">
  136 + <label><input type="checkbox" name="venecia" value="0" /></label>
  137 + <a href="#">RUUKI(3)</a>
  138 + </div>
  139 + <div class="checkbox">
  140 + <label><input type="checkbox" name="venecia" value="0" /></label>
  141 + <a href="#">Venecia(10)</a>
  142 + </div>
  143 + </div>
  144 + </li>
  145 +
  146 + <li>Вес 1 м2 на кг
  147 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  148 + <div class="price_filter">
  149 + <div class="checkbox">
  150 + <label><input type="checkbox" name="venecia" value="0" /></label>
  151 + <a href="#">RUUKI(3)</a>
  152 + </div>
  153 + <div class="checkbox">
  154 + <label><input type="checkbox" name="venecia" value="0" /></label>
  155 + <a href="#">Venecia(10)</a>
  156 + </div>
  157 + </div>
  158 + </li>
  159 +
  160 + <li><span class="width_li_filter">Общая высота профиля, мм</span>
  161 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  162 + <div class="price_filter">
  163 + <div class="checkbox">
  164 + <label><input type="checkbox" name="venecia" value="0" /></label>
  165 + <a href="#">RUUKI(3)</a>
  166 + </div>
  167 + <div class="checkbox">
  168 + <label><input type="checkbox" name="venecia" value="0" /></label>
  169 + <a href="#">Venecia(10)</a>
  170 + </div>
  171 + </div>
  172 + </li>
  173 +
  174 + <li><span>Общая ширина, мм</span>
  175 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  176 + <div class="price_filter">
  177 + <div class="checkbox">
  178 + <label><input type="checkbox" name="venecia" value="0" /></label>
  179 + <a href="#">RUUKI(3)</a>
  180 + </div>
  181 + <div class="checkbox">
  182 + <label><input type="checkbox" name="venecia" value="0" /></label>
  183 + <a href="#">Venecia(10)</a>
  184 + </div>
  185 + </div>
  186 + </li>
  187 +
  188 + <li>Шаг волны, мм
  189 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  190 + <div class="price_filter">
  191 + <div class="checkbox">
  192 + <label><input type="checkbox" name="venecia" value="0" /></label>
  193 + <a href="#">RUUKI(3)</a>
  194 + </div>
  195 + <div class="checkbox">
  196 + <label><input type="checkbox" name="venecia" value="0" /></label>
  197 + <a href="#">Venecia(10)</a>
  198 + </div>
  199 + </div>
  200 + </li>
  201 +
  202 + <li>Полезная ширина, мм
  203 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  204 + <div class="price_filter">
  205 + <div class="checkbox">
  206 + <label><input type="checkbox" name="venecia" value="0" /></label>
  207 + <a href="#">RUUKI(3)</a>
  208 + </div>
  209 + <div class="checkbox">
  210 + <label><input type="checkbox" name="venecia" value="0" /></label>
  211 + <a href="#">Venecia(10)</a>
  212 + </div>
  213 + </div>
  214 + </li>
  215 +
  216 + <li><span class="width_li_filter">Количества цинка, г/м2</span>
  217 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  218 + <div class="price_filter">
  219 + <div class="checkbox">
  220 + <label><input type="checkbox" name="venecia" value="0" /></label>
  221 + <a href="#">RUUKI(3)</a>
  222 + </div>
  223 + <div class="checkbox">
  224 + <label><input type="checkbox" name="venecia" value="0" /></label>
  225 + <a href="#">Venecia(10)</a>
  226 + </div>
  227 + </div>
  228 + </li>
  229 +
  230 + <li>Гарантия
  231 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  232 + <div class="price_filter">
  233 + <div class="checkbox">
  234 + <label><input type="checkbox" name="venecia" value="0" /></label>
  235 + <a href="#">RUUKI(3)</a>
  236 + </div>
  237 + <div class="checkbox">
  238 + <label><input type="checkbox" name="venecia" value="0" /></label>
  239 + <a href="#">Venecia(10)</a>
  240 + </div>
  241 + </div>
  242 + </li>
  243 +
  244 + </ul>
  245 +
  246 + <div class="filter_accept_bloc">
  247 + <button type="submit" class="filter_accept_btn">применить</button>
  248 + <a href="#" class="form_checkbox_reset">сбросить фильтры</a>
37 249 </div>
  250 + </form>
  251 + <!--<div class="product_list">
  252 + <h2 class="title">КАТАЛОГ ТОВАРОВ</h2>
  253 + <ul>
  254 + <li>Битумная черепица
  255 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  256 + <div class="price_filter">
  257 + <a href="#">RUUKI</a>
  258 + <a href="#">Venecia</a>
  259 + </div>
  260 + </li>
  261 +
  262 + <li>Комплектация кровли
  263 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  264 + <div class="price_filter">
  265 + <a href="#">RUUKI</a>
  266 + <a href="#">Venecia</a>
  267 + </div>
  268 + </li>
  269 +
  270 + <li>Водосточные системы
  271 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  272 + <div class="price_filter">
  273 + <a href="#">RUUKI</a>
  274 + <a href="#">Venecia</a>
  275 + </div>
  276 + </li>
  277 +
  278 + <li>Чердачные лестницы
  279 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  280 + <div class="price_filter">
  281 + <a href="#">RUUKI</a>
  282 + <a href="#">Venecia</a>
  283 + </div>
  284 + </li>
  285 +
  286 + <li>Мансардные окна
  287 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  288 + <div class="price_filter">
  289 + <a href="#">RUUKI</a>
  290 + <a href="#">Venecia</a>
  291 + </div>
  292 + </li>
  293 +
  294 + <li>Металлочерепица
  295 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  296 + <div class="price_filter">
  297 + <a href="#">RUUKI</a>
  298 + <a href="#">Venecia</a>
  299 + </div>
  300 + </li>
  301 +
  302 + <li>Плоская кровля
  303 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  304 + <div class="price_filter">
  305 + <a href="#">RUUKI</a>
  306 + <a href="#">Venecia</a>
  307 + </div>
  308 + </li>
  309 +
  310 + <li>Профнастил
  311 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  312 + <div class="price_filter">
  313 + <a href="#">RUUKI</a>
  314 + <a href="#">Venecia</a>
  315 + </div>
  316 + </li>
  317 +
  318 + <li>Ондулин
  319 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  320 + <div class="price_filter">
  321 + <a href="#">RUUKI</a>
  322 + <a href="#">Venecia</a>
  323 + </div>
  324 + </li>
  325 +
  326 + <li>OSB плиты
  327 + <div class="arrow"><img src="/images/head_down.png" alt=""></i></div>
  328 + <div class="price_filter">
  329 + <a href="#">RUUKI</a>
  330 + <a href="#">Venecia</a>
  331 + </div>
  332 + </li>
  333 +
  334 + </ul>
  335 + </div>-->
  336 + </div>
  337 +
  338 +
  339 + </div>
  340 +
  341 + <!-- catalog list with all item cards -->
  342 + <div class="cat_p_catalog_list">
  343 + <div class="title"><?= $category->name?> <span>(<?= $product_count?>)</span></div>
  344 +
  345 + <!-- sort menu -->
  346 + <div class="sort_menu">
  347 +
  348 + <div class="sort_price">
  349 + <span>Сортировка:</span>
  350 + <?= \yii\widgets\LinkSorter::widget([
  351 + 'sort' => $sort,
  352 + 'attributes' => [
  353 + 'name',
  354 + 'price',
  355 + ]
  356 + ]);
  357 + ?>
  358 + <!--
  359 + <select name="sort_price" id="" class="sort_price_select">
  360 + <option value="price">по цене</option>
  361 + <option value="popular">новые</option>
  362 + <option value="sale">по акции</option>
  363 + </select>
  364 + <i class="fa fa-angle-down"></i>-->
  365 + </div>
  366 +
  367 + <div class="show">
  368 + <!--<span>Показывать по:</span>
  369 + <ul>
  370 + <li><a class="active" href="#">24</a></li>
  371 + <li><a href="#">48</a></li>
  372 + <li><a href="#">96</a></li>
  373 + </ul>-->
  374 + </div>
  375 +
  376 + <div class="show_pages">
  377 + <?php if ($pages->totalCount > $pages->pageSize) :?>
  378 + <span>Страница:</span>
  379 + <?= \yii\widgets\LinkPager::widget([
  380 + 'pagination' => $pages,
  381 + 'options' => ['class' => 'pagination pull-right'],
  382 + ]);
  383 + ?>
  384 + <!--<i class="fa fa-caret-right"></i>-->
38 385 <?php endif?>
39 386 </div>
40   - <!-- $this->params['breadcrumbs'][] = ['label' => $parent->name, 'url' => ['catalog/category', 'alias' => $parent->alias]];-->
41   - <?php endforeach?>
42 387  
43   - </div><!-- end of 3 colons for items -->
  388 + </div>
  389 +
  390 +
  391 + <div class="cat_p_item_card_list">
  392 + <div class="novelty">
  393 + <div class="content">
  394 + <div class="novelty_cont">
  395 + <?php foreach($products as $product) :?>
  396 + <?php require(__DIR__ .'/product_item.php')?>
  397 + <?php endforeach?>
  398 + </div>
  399 +
  400 + <?php if ($pages->totalCount > $pages->pageSize) :?>
  401 + <!-- LOAD MORE BUTTON -->
  402 + <button class="load_more_btn">Загрузить еще <?= $per_page?> товара</button>
  403 +
  404 + <div class="show_pages">
  405 + Страница:
  406 + <?= \yii\widgets\LinkPager::widget([
  407 + 'pagination' => $pages,
  408 + 'options' => ['class' => 'pagination pull-right'],
  409 + ]);
  410 + ?>
  411 + <!--<i class="fa fa-caret-right"></i>-->
  412 + </div>
  413 + <?php endif?>
  414 + <hr>
  415 +
  416 + <div class="description">
  417 + <h2>Преимущества металлочерепицы:</h2>
  418 + <p>
  419 + На рынке стройматериалов представлено множество кровельных покрытий, от привычного всем рубероида до современной гибкой черепицы на основе стекловолоконных композитов. Все они имеют свои преимущества и сферы применения. Но металлочерепица на протяжении многих лет удерживает лидерские позиции по уровню продаж, и этzо объясняется несколькими причинами:
  420 + </p>
  421 + <h4>Кровля из металлочерепицы</h4>
  422 + <p class="margin_bottom_20">
  423 + <span class="bold">1. Малый вес.</span> Эта характеристика дает ощутимую финансовую выгоду. Вам не придется усиливать стропильные конструкции, значит, удастся сэкономить на пиломатериалах. Легкость металлочерепицы для кровли упрощает транспортировку и монтаж, позволяет отказаться от дорогостоящих услуг подъемной и грузовой спецтехники.
  424 + </p>
  425 + <p class="margin_bottom_20">
  426 + <span class="bold">2. Прочность.</span> В отличие от рулонных и гибких материалов, металлочерепица устойчива к механическим повреждениям. Ее крайне сложно поцарапать и практически невозможно сломать. Высокие технические характеристики говорят и о хорошей несущей способности материала.
  427 + </p>
  428 + <p class="margin_bottom_20">
  429 + <span class="bold">3. Эстетичность.</span> Металлочерепица придает крыше законченный, аккуратный вид. Дом выглядит просто роскошно. Подбирая оригинальные цветовые сочетания, дизайнерам удается с помощью
  430 + </p>
  431 +
  432 + <div class="empty_padding_400"></div>
  433 + </div>
  434 + </div>
  435 + </div>
  436 + </div>
  437 + </div>
  438 +</div>
44 439  
45   -</div>
46 440 \ No newline at end of file
... ...
frontend/views/вертска/js/my_scripts.js
... ... @@ -2,16 +2,6 @@ $(document).ready(function(){
2 2 // ion tabs
3 3 $.ionTabs("#tabs_1");
4 4  
5   - // price rangeslider (filter price slider)
6   - $("#example_id").ionRangeSlider({
7   - type: "double",
8   - min: 0,
9   - max: 500,
10   - from: 50,
11   - to: 450,
12   - grid: false
13   - });
14   -
15 5 // ion checkradio init
16 6 $("input[type='radio'], input[type='checkbox']").ionCheckRadio();
17 7  
... ...
frontend/web/css/style.css
... ... @@ -501,6 +501,11 @@ button:focus {outline: none;}
501 501 padding-bottom:60px;
502 502 color:#333333;
503 503 }
  504 +.novelty_cont:after {
  505 + display: block;
  506 + content: '';
  507 + clear: both;
  508 +}
504 509 .novelty_cont .item{
505 510 border:1px solid #cdd1d9;
506 511 border-bottom:none;
... ...