Completed
Push — master ( 42b664...be56d0 )
by Scott
02:51
created

Product::validateOptions()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 23
rs 8.5906
cc 5
eloc 13
nc 4
nop 0
1
<?php namespace Bedard\Shop\Models;
2
3
use DB;
4
use Flash;
5
use Lang;
6
use Model;
7
use October\Rain\Database\Builder;
8
use October\Rain\Database\ModelException;
9
use Queue;
10
11
/**
12
 * Product Model.
13
 */
14
class Product extends Model
15
{
16
    use \Bedard\Shop\Traits\Subqueryable,
17
        \October\Rain\Database\Traits\Purgeable,
18
        \October\Rain\Database\Traits\Validation;
19
20
    /**
21
     * @var string The database table used by the model.
22
     */
23
    public $table = 'bedard_shop_products';
24
25
    /**
26
     * @var array Default attributes
27
     */
28
    public $attributes = [
29
        'base_price' => 0,
30
    ];
31
32
    /**
33
     * @var array Guarded fields
34
     */
35
    protected $guarded = ['*'];
36
37
    /**
38
     * @var array Fillable fields
39
     */
40
    protected $fillable = [
41
        'categoriesList',
42
        'name',
43
        'base_price',
44
        'optionsInventories',
45
        'slug',
46
    ];
47
48
    /**
49
     * @var array Purgeable fields
50
     */
51
    public $purgeable = [
52
        'categoriesList',
53
        'optionsInventories',
54
    ];
55
56
    /**
57
     * @var array Relations
58
     */
59
    public $belongsToMany = [
60
        'categories' => [
61
            'Bedard\Shop\Models\Category',
62
            'table' => 'bedard_shop_category_product',
63
            'conditions' => 'is_inherited = 0',
64
        ],
65
        'discounts' => [
66
            'Bedard\Shop\Models\Discount',
67
            'table' => 'bedard_shop_discount_product',
68
        ],
69
    ];
70
71
    public $hasMany = [
72
        'inventories' => [
73
            'Bedard\Shop\Models\Inventory',
74
            'delete' => true,
75
        ],
76
        'options' => [
77
            'Bedard\Shop\Models\Option',
78
            'delete' => true,
79
        ],
80
        'prices' => [
81
            'Bedard\Shop\Models\Price',
82
            'delete' => true,
83
        ],
84
    ];
85
86
    public $hasOne = [
87
        'current_price' => [
88
            'Bedard\Shop\Models\Price',
89
            'scope' => 'isActive',
90
            'order' => 'price asc',
91
        ],
92
    ];
93
94
    /**
95
     * @var array Validation
96
     */
97
    public $rules = [
98
        'name' => 'required',
99
        'base_price' => 'required|numeric|min:0',
100
        'slug' => 'required|unique:bedard_shop_products',
101
    ];
102
103
    /**
104
     * After save.
105
     *
106
     * @return void
107
     */
108
    public function afterSave()
109
    {
110
        $this->saveBasePrice();
111
        $this->saveCategoryRelationships();
112
        $this->saveOptionAndInventoryRelationships();
113
    }
114
115
    /**
116
     * After validate.
117
     *
118
     * @return void
119
     */
120
    public function afterValidate()
121
    {
122
        $this->validateOptions();
123
    }
124
125
    /**
126
     * Get the categories options.
127
     *
128
     * @return array
129
     */
130
    public function getCategoriesListOptions()
131
    {
132
        return Category::orderBy('name')->lists('name', 'id');
133
    }
134
135
    /**
136
     * Get the categories that are directly related to this product.
137
     *
138
     * @return void
139
     */
140
    public function getCategoriesListAttribute()
141
    {
142
        return $this->categories()->lists('id');
143
    }
144
145
    /**
146
     * Update of create the related base price model.
147
     *
148
     * @return void
149
     */
150
    public function saveBasePrice()
151
    {
152
        Price::updateOrCreate(
153
            ['product_id' => $this->id, 'discount_id' => null],
154
            ['price' => $this->base_price]
155
        );
156
    }
157
158
    /**
159
     * Sync the categories checkboxlist with the category relationship.
160
     *
161
     * @return void
162
     */
163
    public function saveCategoryRelationships()
164
    {
165
        $categoryIds = $this->getOriginalPurgeValue('categoriesList');
166
167
        if (is_array($categoryIds)) {
168
            $this->categories()->sync($categoryIds);
169
        }
170
171
        $this->syncInheritedCategories();
172
    }
173
174
    /**
175
     * Save the options and inventories.
176
     *
177
     * @return void
178
     */
179
    public function saveOptionAndInventoryRelationships()
180
    {
181
        $data = $this->getOriginalPurgeValue('optionsInventories');
182
183
        if (is_array($data['options'])) {
184
            $options = $this->saveRelatedOptions($data['options']);
0 ignored issues
show
Unused Code introduced by
$options is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
185
        }
186
    }
187
188
    /**
189
     * Save an array of related options.
190
     *
191
     * @param  array  $options
192
     * @return array
193
     */
194
    protected function saveRelatedOptions(array $options)
195
    {
196
        foreach ($options as &$option) {
197
            $model = $option['id'] !== null
198
                ? Option::firstOrNew(['id' => $option['id']])
199
                : new Option;
200
201
            $model->product_id = $this->id;
202
            $model->fill($option);
203
            $model->save();
204
205
            $option['id'] = $model->id;
206
207
            if (is_array($option['values'])) {
208
                $this->saveRelatedOptionValues($model, $option['values']);
209
            }
210
        }
211
212
        return $options;
213
    }
214
215
    /**
216
     * Save related option values.
217
     *
218
     * @param  Option $option
219
     * @param  array  $values
220
     * @return void
221
     */
222
    protected function saveRelatedOptionValues(Option $option, array $values)
223
    {
224
        foreach ($values as $value) {
225
            $model = $value['id'] !== null
226
                ? OptionValue::firstOrNew(['id' => $value['id']])
227
                : new OptionValue;
228
229
            $model->option_id = $option->id;
230
            $model->fill($value);
231
            $model->save();
232
        }
233
    }
234
235
    /**
236
     * Left joins a subquery containing the product price.
237
     *
238
     * @param  \October\Rain\Database\Builder   $query
239
     * @return \October\Rain\Database\Builder
240
     */
241
    public function scopeJoinPrice(Builder $query)
242
    {
243
        $alias = 'prices';
244
        $grammar = $query->getQuery()->getGrammar();
245
246
        $subquery = Price::isActive()
247
            ->addselect('bedard_shop_prices.product_id')
248
            ->selectRaw('MIN('.$grammar->wrap('bedard_shop_prices.price').') as '.$grammar->wrap('price'))
249
            ->groupBy('bedard_shop_prices.product_id');
250
251
        return $query
252
            ->addSelect($alias.'.price')
253
            ->joinSubquery($subquery, $alias, 'bedard_shop_products.id', '=', $alias.'.product_id');
254
    }
255
256
    /**
257
     * Sync the inherited categories of all products.
258
     *
259
     * @return void
260
     */
261
    public static function syncAllInheritedCategories()
262
    {
263
        Queue::push(function ($job) {
264
            $data = [];
265
            $products = Product::with('categories')->get();
266
            $categoryTree = Category::getParentCategoryIds();
267
268
            foreach ($products as $product) {
269
                $inheritedCategoryIds = [];
270
                foreach ($product->categories as $category) {
271
                    if (array_key_exists($category->id, $categoryTree)) {
272
                        $inheritedCategoryIds = array_merge($inheritedCategoryIds, $categoryTree[$category->id]);
273
                    }
274
                }
275
276
                foreach (array_unique($inheritedCategoryIds) as $categoryId) {
277
                    $data[] = [
278
                        'category_id' => $categoryId,
279
                        'product_id' => $product->id,
280
                        'is_inherited' => 1,
281
                    ];
282
                }
283
            }
284
285
            DB::table('bedard_shop_category_product')->whereIsInherited(1)->delete();
286
            DB::table('bedard_shop_category_product')->insert($data);
287
            Discount::syncAllPrices();
288
289
            $job->delete();
290
        });
291
    }
292
293
    /**
294
     * Sync a product with it's inherited categories.
295
     *
296
     * @return void
297
     */
298
    public function syncInheritedCategories()
299
    {
300
        $data = [];
301
        $categoryIds = $this->categories()->lists('id');
302
        $parentIds = Category::isParentOf($categoryIds)->lists('id');
303
        foreach ($parentIds as $parentId) {
304
            $data[] = [
305
                'category_id' => $parentId,
306
                'product_id' => $this->id,
307
                'is_inherited' => true,
308
            ];
309
        }
310
311
        DB::table('bedard_shop_category_product')->whereProductId($this->id)->whereIsInherited(1)->delete();
312
        DB::table('bedard_shop_category_product')->insert($data);
313
314
        $categoryScope = array_merge($categoryIds, $parentIds);
315
        Discount::syncProductPrice($this, $categoryScope);
316
    }
317
318
    public function validateOptions()
319
    {
320
        if (! is_array($this->optionsInventories) ||
321
            ! is_array($this->optionsInventories['options'])) {
322
            return;
323
        }
324
325
        $names = [];
326
        foreach ($this->optionsInventories['options'] as $option) {
327
            $name = strtolower(trim($option['name']));
328
            // Validate the option
329
            $option = new Option($option);
330
            $option->validate();
331
332
            // Names must be unique
333
            if (in_array($name, $names)) {
334
                Flash::error(Lang::get('bedard.shop::lang.products.form.duplicate_options_error'));
335
                throw new ModelException($this);
336
            }
337
338
            $names[] = $name;
339
        }
340
    }
341
}
342