Completed
Push — master ( 71d62b...9fb76c )
by Scott
04:15
created

Product::saveRelatedInventories()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 1
nc 1
nop 1
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
            'order' => 'sort_order',
80
        ],
81
        'prices' => [
82
            'Bedard\Shop\Models\Price',
83
            'delete' => true,
84
        ],
85
    ];
86
87
    public $hasOne = [
88
        'current_price' => [
89
            'Bedard\Shop\Models\Price',
90
            'scope' => 'isActive',
91
            'order' => 'price asc',
92
        ],
93
    ];
94
95
    /**
96
     * @var array Validation
97
     */
98
    public $rules = [
99
        'name' => 'required',
100
        'base_price' => 'required|numeric|min:0',
101
        'slug' => 'required|unique:bedard_shop_products',
102
    ];
103
104
    /**
105
     * After save.
106
     *
107
     * @return void
108
     */
109
    public function afterSave()
110
    {
111
        $this->saveBasePrice();
112
        $this->saveCategoryRelationships();
113
        $this->saveOptionAndInventoryRelationships();
114
    }
115
116
    /**
117
     * After validate.
118
     *
119
     * @return void
120
     */
121
    public function afterValidate()
122
    {
123
        $this->validateOptions();
124
        $this->validateInventories();
125
    }
126
127
    /**
128
     * Delete inventories that have the is_deleted flag.
129
     *
130
     * @param  array $inventories
131
     * @return array
132
     */
133
    protected function deleteRelatedInventories($inventories)
134
    {
135
        // @todo
136
    }
137
138
    /**
139
     * Delete options that have the is_deleted flag.
140
     *
141
     * @param  array $options
142
     * @return array
143
     */
144
    protected function deleteRelatedOptions($options)
145
    {
146
        return array_filter($options, function ($option) {
147
            if ($option['id'] !== null && $option['is_deleted']) {
148
                Option::find($option['id'])->delete();
149
            }
150
151
            return ! $option['is_deleted'];
152
        });
153
    }
154
155
    /**
156
     * Get the categories options.
157
     *
158
     * @return array
159
     */
160
    public function getCategoriesListOptions()
161
    {
162
        return Category::orderBy('name')->lists('name', 'id');
163
    }
164
165
    /**
166
     * Get the categories that are directly related to this product.
167
     *
168
     * @return void
169
     */
170
    public function getCategoriesListAttribute()
171
    {
172
        return $this->categories()->lists('id');
173
    }
174
175
    /**
176
     * Update of create the related base price model.
177
     *
178
     * @return void
179
     */
180
    public function saveBasePrice()
181
    {
182
        Price::updateOrCreate(
183
            ['product_id' => $this->id, 'discount_id' => null],
184
            ['price' => $this->base_price]
185
        );
186
    }
187
188
    /**
189
     * Sync the categories checkboxlist with the category relationship.
190
     *
191
     * @return void
192
     */
193
    public function saveCategoryRelationships()
194
    {
195
        $categoryIds = $this->getOriginalPurgeValue('categoriesList');
196
197
        if (is_array($categoryIds)) {
198
            $this->categories()->sync($categoryIds);
199
        }
200
201
        $this->syncInheritedCategories();
202
    }
203
204
    /**
205
     * Save the options and inventories.
206
     *
207
     * @return void
208
     */
209
    public function saveOptionAndInventoryRelationships()
210
    {
211
        $data = $this->getOriginalPurgeValue('optionsInventories');
212
213
        if (is_array($data['options'])) {
214
            $options = $data['options'];
215
            $options = $this->deleteRelatedOptions($options);
216
            $options = $this->saveRelatedOptions($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...
217
        }
218
219
        if (is_array($data['inventories'])) {
220
            $inventories = $data['inventories'];
221
            // $inventories = $this->deleteRelatedInventories($inventories);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
222
            $inventories = $this->saveRelatedInventories($inventories);
0 ignored issues
show
Unused Code introduced by
$inventories 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...
223
        }
224
    }
225
226
    /**
227
     * Save an array of realted inventories.
228
     *
229
     * @param  array  $inventories
230
     * @return array
231
     */
232
    protected function saveRelatedInventories(array $inventories)
233
    {
234
        // @todo
235
    }
236
237
    /**
238
     * Save an array of related options.
239
     *
240
     * @param  array  $options
241
     * @return array
242
     */
243
    protected function saveRelatedOptions(array $options)
244
    {
245
        foreach ($options as &$option) {
246
            $model = $option['id'] !== null
247
                ? Option::firstOrNew(['id' => $option['id']])
248
                : new Option;
249
250
            $model->product_id = $this->id;
251
            $model->fill($option);
252
253
            $sessionKey = uniqid('session_key', true);
254
            if (is_array($option['values'])) {
255
                $this->saveRelatedOptionValues($model, $option['values'], $sessionKey);
256
            }
257
258
            $model->save(null, $sessionKey);
259
        }
260
261
        return $options;
262
    }
263
264
    /**
265
     * Save related option values.
266
     *
267
     * @param  Option $option
268
     * @param  array  $values
269
     * @param  string $sessionKey
270
     * @return void
271
     */
272
    protected function saveRelatedOptionValues(Option &$option, array $values, $sessionKey)
273
    {
274
        foreach ($values as $value) {
275
            $model = $value['id'] !== null
276
                ? OptionValue::firstOrNew(['id' => $value['id']])
277
                : new OptionValue;
278
279
            $model->fill($value);
280
            $model->save();
281
282
            $option->values()->add($model, $sessionKey);
283
        }
284
    }
285
286
    /**
287
     * Left joins a subquery containing the product price.
288
     *
289
     * @param  \October\Rain\Database\Builder   $query
290
     * @return \October\Rain\Database\Builder
291
     */
292
    public function scopeJoinPrice(Builder $query)
293
    {
294
        $alias = 'prices';
295
        $grammar = $query->getQuery()->getGrammar();
296
297
        $subquery = Price::isActive()
298
            ->addselect('bedard_shop_prices.product_id')
299
            ->selectRaw('MIN('.$grammar->wrap('bedard_shop_prices.price').') as '.$grammar->wrap('price'))
300
            ->groupBy('bedard_shop_prices.product_id');
301
302
        return $query
303
            ->addSelect($alias.'.price')
304
            ->joinSubquery($subquery, $alias, 'bedard_shop_products.id', '=', $alias.'.product_id');
305
    }
306
307
    /**
308
     * Sync the inherited categories of all products.
309
     *
310
     * @return void
311
     */
312
    public static function syncAllInheritedCategories()
313
    {
314
        Queue::push(function ($job) {
315
            $data = [];
316
            $products = Product::with('categories')->get();
317
            $categoryTree = Category::getParentCategoryIds();
318
319
            foreach ($products as $product) {
320
                $inheritedCategoryIds = [];
321
                foreach ($product->categories as $category) {
322
                    if (array_key_exists($category->id, $categoryTree)) {
323
                        $inheritedCategoryIds = array_merge($inheritedCategoryIds, $categoryTree[$category->id]);
324
                    }
325
                }
326
327
                foreach (array_unique($inheritedCategoryIds) as $categoryId) {
328
                    $data[] = [
329
                        'category_id' => $categoryId,
330
                        'product_id' => $product->id,
331
                        'is_inherited' => 1,
332
                    ];
333
                }
334
            }
335
336
            DB::table('bedard_shop_category_product')->whereIsInherited(1)->delete();
337
            DB::table('bedard_shop_category_product')->insert($data);
338
            Discount::syncAllPrices();
339
340
            $job->delete();
341
        });
342
    }
343
344
    /**
345
     * Sync a product with it's inherited categories.
346
     *
347
     * @return void
348
     */
349
    public function syncInheritedCategories()
350
    {
351
        $data = [];
352
        $categoryIds = $this->categories()->lists('id');
353
        $parentIds = Category::isParentOf($categoryIds)->lists('id');
354
        foreach ($parentIds as $parentId) {
355
            $data[] = [
356
                'category_id' => $parentId,
357
                'product_id' => $this->id,
358
                'is_inherited' => true,
359
            ];
360
        }
361
362
        DB::table('bedard_shop_category_product')->whereProductId($this->id)->whereIsInherited(1)->delete();
363
        DB::table('bedard_shop_category_product')->insert($data);
364
365
        $categoryScope = array_merge($categoryIds, $parentIds);
366
        Discount::syncProductPrice($this, $categoryScope);
367
    }
368
369
    /**
370
     * Validate inventories.
371
     *
372
     * @throws \October\Rain\Database\ModelException
373
     * @return void
374
     */
375 View Code Duplication
    protected function validateInventories()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
    {
377
        if (! is_array($this->optionsInventories) ||
378
            ! is_array($this->optionsInventories['options'])) {
379
            return;
380
        }
381
382
        $takenValueCombinations = [];
383
        foreach ($this->optionsInventories['inventories'] as $inventory) {
384
            // validate the inventory
385
            $model = new Inventory($inventory);
386
            $model->validate();
387
388
            // validate that the value combinations are unique
389
            sort($inventory['valueIds']);
390
            $valueCombination = json_encode($inventory['valueIds']);
391
392
            if (in_array($valueCombination, $takenValueCombinations)) {
393
                Flash::error(Lang::get('bedard.shop::lang.products.form.duplicate_inventories_error'));
394
                throw new ModelException($this);
395
            }
396
397
            $takenValueCombinations[] = $valueCombination;
398
        }
399
    }
400
401
    /**
402
     * Validate options.
403
     *
404
     * @throws \October\Rain\Database\ModelException
405
     * @return void
406
     */
407 View Code Duplication
    protected function validateOptions()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
408
    {
409
        if (! is_array($this->optionsInventories) ||
410
            ! is_array($this->optionsInventories['options'])) {
411
            return;
412
        }
413
414
        $names = [];
415
        foreach ($this->optionsInventories['options'] as $option) {
416
            $name = strtolower(trim($option['name']));
417
            // validate the option
418
            $model = new Option($option);
419
            $model->validate();
420
421
            // validate that names are unique
422
            if (in_array($name, $names)) {
423
                Flash::error(Lang::get('bedard.shop::lang.products.form.duplicate_options_error'));
424
                throw new ModelException($this);
425
            }
426
427
            $names[] = $name;
428
        }
429
    }
430
}
431