Completed
Push — master ( c99589...beb3f5 )
by Scott
02:24
created

Product::setPlainDescription()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
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
        'description_html' => '',
31
        'description_plain' => '',
32
    ];
33
34
    /**
35
     * @var array Attribute casting
36
     */
37
    protected $casts = [
38
        'price' => 'float',
39
        'base_price' => 'float',
40
    ];
41
42
    /**
43
     * @var array Guarded fields
44
     */
45
    protected $guarded = ['*'];
46
47
    /**
48
     * @var array Fillable fields
49
     */
50
    protected $fillable = [
51
        'base_price',
52
        'categoriesList',
53
        'description_html',
54
        'description_plain',
55
        'name',
56
        'optionsInventories',
57
        'slug',
58
    ];
59
60
    /**
61
     * @var array Purgeable fields
62
     */
63
    public $purgeable = [
64
        'categoriesList',
65
        'optionsInventories',
66
    ];
67
68
    /**
69
     * @var array Relations
70
     */
71
    public $attachMany = [
72
        'images' => 'System\Models\File',
73
        'thumbnails' => 'System\Models\File',
74
    ];
75
76
    public $belongsToMany = [
77
        'categories' => [
78
            'Bedard\Shop\Models\Category',
79
            'table' => 'bedard_shop_category_product',
80
            'conditions' => 'is_inherited = 0',
81
        ],
82
        'discounts' => [
83
            'Bedard\Shop\Models\Discount',
84
            'table' => 'bedard_shop_discount_product',
85
        ],
86
    ];
87
88
    public $hasMany = [
89
        'inventories' => [
90
            'Bedard\Shop\Models\Inventory',
91
            'delete' => true,
92
        ],
93
        'options' => [
94
            'Bedard\Shop\Models\Option',
95
            'delete' => true,
96
            'order' => 'sort_order',
97
        ],
98
        'prices' => [
99
            'Bedard\Shop\Models\Price',
100
            'delete' => true,
101
        ],
102
    ];
103
104
    public $hasOne = [
105
        'current_price' => [
106
            'Bedard\Shop\Models\Price',
107
            'scope' => 'isActive',
108
            'order' => 'price asc',
109
        ],
110
    ];
111
112
    /**
113
     * @var array Validation
114
     */
115
    public $rules = [
116
        'name' => 'required',
117
        'base_price' => 'required|numeric|min:0',
118
        'slug' => 'required|unique:bedard_shop_products',
119
    ];
120
121
    /**
122
     * After save.
123
     *
124
     * @return void
125
     */
126
    public function afterSave()
127
    {
128
        $this->saveBasePrice();
129
        $this->saveCategoryRelationships();
130
        $this->saveOptionAndInventoryRelationships();
131
    }
132
133
    /**
134
     * After validate.
135
     *
136
     * @return void
137
     */
138
    public function afterValidate()
139
    {
140
        $this->validateOptions();
141
        $this->validateInventories();
142
    }
143
144
    /**
145
     * Before save.
146
     *
147
     * @return void
148
     */
149
    public function beforeSave()
150
    {
151
        $this->setPlainDescription();
152
    }
153
154
    /**
155
     * Delete inventories that have the is_deleted flag.
156
     *
157
     * @param  array $inventories
158
     * @return array
159
     */
160
    protected function deleteRelatedInventories($inventories)
161
    {
162
        return array_filter($inventories, function ($inventory) {
163
            if ($inventory['id'] !== null && $inventory['is_deleted']) {
164
                Inventory::find($inventory['id'])->delete();
165
            }
166
167
            return ! $inventory['is_deleted'];
168
        });
169
    }
170
171
    /**
172
     * Delete options that have the is_deleted flag.
173
     *
174
     * @param  array $options
175
     * @return array
176
     */
177
    protected function deleteRelatedOptions($options)
178
    {
179
        return array_filter($options, function ($option) {
180
            if ($option['id'] !== null && $option['is_deleted']) {
181
                Option::find($option['id'])->delete();
182
            }
183
184
            return ! $option['is_deleted'];
185
        });
186
    }
187
188
    /**
189
     * Get the categories options.
190
     *
191
     * @return array
192
     */
193
    public function getCategoriesListOptions()
194
    {
195
        return Category::orderBy('name')->lists('name', 'id');
196
    }
197
198
    /**
199
     * Get the categories that are directly related to this product.
200
     *
201
     * @return void
202
     */
203
    public function getCategoriesListAttribute()
204
    {
205
        return $this->categories()->lists('id');
206
    }
207
208
    /**
209
     * Update of create the related base price model.
210
     *
211
     * @return void
212
     */
213
    public function saveBasePrice()
214
    {
215
        Price::updateOrCreate(
216
            ['product_id' => $this->id, 'discount_id' => null],
217
            ['price' => $this->base_price]
218
        );
219
    }
220
221
    /**
222
     * Sync the categories checkboxlist with the category relationship.
223
     *
224
     * @return void
225
     */
226
    public function saveCategoryRelationships()
227
    {
228
        $categoryIds = $this->getOriginalPurgeValue('categoriesList');
229
230
        if (is_array($categoryIds)) {
231
            $this->categories()->sync($categoryIds);
232
        }
233
234
        $this->syncInheritedCategories();
235
    }
236
237
    /**
238
     * Save the options and inventories.
239
     *
240
     * @return void
241
     */
242
    public function saveOptionAndInventoryRelationships()
243
    {
244
        $data = $this->getOriginalPurgeValue('optionsInventories');
245
246
        if (is_array($data['options'])) {
247
            $options = $data['options'];
248
            $options = $this->deleteRelatedOptions($options);
249
            $this->saveRelatedOptions($options);
250
        }
251
252
        if (is_array($data['inventories'])) {
253
            $inventories = $data['inventories'];
254
            $inventories = $this->deleteRelatedInventories($inventories);
255
            $this->saveRelatedInventories($inventories);
256
        }
257
    }
258
259
    /**
260
     * Save an array of realted inventories.
261
     *
262
     * @param  array  $inventories
263
     * @return array
264
     */
265
    protected function saveRelatedInventories(array $inventories)
266
    {
267
        foreach ($inventories as $inventory) {
268
            $model = $inventory['id'] !== null
269
                ? Inventory::firstOrNew(['id' => $inventory['id']])
270
                : new Inventory;
271
272
            $inventory['product_id'] = $this->id;
273
            $model->fill($inventory);
274
            $model->save();
275
        }
276
    }
277
278
    /**
279
     * Save an array of related options.
280
     *
281
     * @param  array  $options
282
     * @return array
283
     */
284
    protected function saveRelatedOptions(array $options)
285
    {
286
        foreach ($options as &$option) {
287
            $model = $option['id'] !== null
288
                ? Option::firstOrNew(['id' => $option['id']])
289
                : new Option;
290
291
            $model->product_id = $this->id;
292
            $model->fill($option);
293
294
            $sessionKey = uniqid('session_key', true);
295
            if (is_array($option['values'])) {
296
                $this->saveRelatedOptionValues($model, $option['values'], $sessionKey);
297
            }
298
299
            $model->save(null, $sessionKey);
300
        }
301
302
        return $options;
303
    }
304
305
    /**
306
     * Save related option values.
307
     *
308
     * @param  Option $option
309
     * @param  array  $values
310
     * @param  string $sessionKey
311
     * @return void
312
     */
313
    protected function saveRelatedOptionValues(Option &$option, array $values, $sessionKey)
314
    {
315
        foreach ($values as $value) {
316
            $model = $value['id'] !== null
317
                ? OptionValue::firstOrNew(['id' => $value['id']])
318
                : new OptionValue;
319
320
            $model->fill($value);
321
            $model->save();
322
323
            $option->values()->add($model, $sessionKey);
324
        }
325
    }
326
327
    /**
328
     * Left joins a subquery containing the product price.
329
     *
330
     * @param  \October\Rain\Database\Builder   $query
331
     * @return \October\Rain\Database\Builder
332
     */
333
    public function scopeJoinPrice(Builder $query)
334
    {
335
        $alias = 'prices';
336
        $grammar = $query->getQuery()->getGrammar();
337
338
        $subquery = Price::isActive()
339
            ->addselect('bedard_shop_prices.product_id')
340
            ->selectRaw('MIN('.$grammar->wrap('bedard_shop_prices.price').') as '.$grammar->wrap('price'))
341
            ->groupBy('bedard_shop_prices.product_id');
342
343
        return $query
344
            ->addSelect($alias.'.price')
345
            ->joinSubquery($subquery, $alias, 'bedard_shop_products.id', '=', $alias.'.product_id');
346
    }
347
348
    /**
349
     * Set the plain text description_html.
350
     *
351
     * @return void
352
     */
353
    protected function setPlainDescription()
354
    {
355
        $this->description_plain = strip_tags($this->description_html);
356
    }
357
358
    /**
359
     * Sync the inherited categories of all products.
360
     *
361
     * @return void
362
     */
363
    public static function syncAllInheritedCategories()
364
    {
365
        Queue::push(function ($job) {
366
            $data = [];
367
            $products = Product::with('categories')->get();
368
            $categoryTree = Category::getParentCategoryIds();
369
370
            foreach ($products as $product) {
371
                $inheritedCategoryIds = [];
372
                foreach ($product->categories as $category) {
373
                    if (array_key_exists($category->id, $categoryTree)) {
374
                        $inheritedCategoryIds = array_merge($inheritedCategoryIds, $categoryTree[$category->id]);
375
                    }
376
                }
377
378
                foreach (array_unique($inheritedCategoryIds) as $categoryId) {
379
                    $data[] = [
380
                        'category_id' => $categoryId,
381
                        'product_id' => $product->id,
382
                        'is_inherited' => 1,
383
                    ];
384
                }
385
            }
386
387
            DB::table('bedard_shop_category_product')->whereIsInherited(1)->delete();
388
            DB::table('bedard_shop_category_product')->insert($data);
389
            Discount::syncAllPrices();
390
391
            $job->delete();
392
        });
393
    }
394
395
    /**
396
     * Sync a product with it's inherited categories.
397
     *
398
     * @return void
399
     */
400
    public function syncInheritedCategories()
401
    {
402
        $data = [];
403
        $categoryIds = $this->categories()->lists('id');
404
        $parentIds = Category::isParentOf($categoryIds)->lists('id');
405
        foreach ($parentIds as $parentId) {
406
            $data[] = [
407
                'category_id' => $parentId,
408
                'product_id' => $this->id,
409
                'is_inherited' => true,
410
            ];
411
        }
412
413
        DB::table('bedard_shop_category_product')->whereProductId($this->id)->whereIsInherited(1)->delete();
414
        DB::table('bedard_shop_category_product')->insert($data);
415
416
        $categoryScope = array_merge($categoryIds, $parentIds);
417
        Discount::syncProductPrice($this, $categoryScope);
418
    }
419
420
    /**
421
     * Validate inventories.
422
     *
423
     * @throws \October\Rain\Database\ModelException
424
     * @return void
425
     */
426 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...
427
    {
428
        if (! is_array($this->optionsInventories) ||
429
            ! is_array($this->optionsInventories['options'])) {
430
            return;
431
        }
432
433
        $takenValueCombinations = [];
434
        foreach ($this->optionsInventories['inventories'] as $inventory) {
435
            // validate the inventory
436
            $model = new Inventory($inventory);
437
            $model->validate();
438
439
            // validate that the value combinations are unique
440
            sort($inventory['valueIds']);
441
            $valueCombination = json_encode($inventory['valueIds']);
442
443
            if (in_array($valueCombination, $takenValueCombinations)) {
444
                Flash::error(Lang::get('bedard.shop::lang.products.form.duplicate_inventories_error'));
445
                throw new ModelException($this);
446
            }
447
448
            $takenValueCombinations[] = $valueCombination;
449
        }
450
    }
451
452
    /**
453
     * Validate options.
454
     *
455
     * @throws \October\Rain\Database\ModelException
456
     * @return void
457
     */
458 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...
459
    {
460
        if (! is_array($this->optionsInventories) ||
461
            ! is_array($this->optionsInventories['options'])) {
462
            return;
463
        }
464
465
        $names = [];
466
        foreach ($this->optionsInventories['options'] as $option) {
467
            // validate the option
468
            $model = new Option($option);
469
            $model->validate();
470
471
            // validate that names are unique
472
            $name = strtolower(trim($option['name']));
473
474
            if (in_array($name, $names)) {
475
                Flash::error(Lang::get('bedard.shop::lang.products.form.duplicate_options_error'));
476
                throw new ModelException($this);
477
            }
478
479
            $names[] = $name;
480
        }
481
    }
482
}
483