Completed
Push — master ( 74b394...36090e )
by Scott
04:15
created

Product::deleteRelatedOptions()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 3
eloc 5
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
        ],
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
     * Delete options that have the is_deleted flag
127
     *
128
     * @param  [type] $options [description]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
129
     * @return [type]          [description]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
130
     */
131
    protected function deleteRelatedOptions($options)
132
    {
133
        return array_filter($options, function($option) {
134
            if ($option['id'] !== null && $option['is_deleted']) {
135
                Option::find($option['id'])->delete();
136
            }
137
138
            return ! $option['is_deleted'];
139
        });
140
    }
141
142
    /**
143
     * Get the categories options.
144
     *
145
     * @return array
146
     */
147
    public function getCategoriesListOptions()
148
    {
149
        return Category::orderBy('name')->lists('name', 'id');
150
    }
151
152
    /**
153
     * Get the categories that are directly related to this product.
154
     *
155
     * @return void
156
     */
157
    public function getCategoriesListAttribute()
158
    {
159
        return $this->categories()->lists('id');
160
    }
161
162
    /**
163
     * Update of create the related base price model.
164
     *
165
     * @return void
166
     */
167
    public function saveBasePrice()
168
    {
169
        Price::updateOrCreate(
170
            ['product_id' => $this->id, 'discount_id' => null],
171
            ['price' => $this->base_price]
172
        );
173
    }
174
175
    /**
176
     * Sync the categories checkboxlist with the category relationship.
177
     *
178
     * @return void
179
     */
180
    public function saveCategoryRelationships()
181
    {
182
        $categoryIds = $this->getOriginalPurgeValue('categoriesList');
183
184
        if (is_array($categoryIds)) {
185
            $this->categories()->sync($categoryIds);
186
        }
187
188
        $this->syncInheritedCategories();
189
    }
190
191
    /**
192
     * Save the options and inventories.
193
     *
194
     * @return void
195
     */
196
    public function saveOptionAndInventoryRelationships()
197
    {
198
        $data = $this->getOriginalPurgeValue('optionsInventories');
199
200
        if (is_array($data['options'])) {
201
            $options = $data['options'];
202
            $options = $this->deleteRelatedOptions($options);
203
            $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...
204
        }
205
    }
206
207
    /**
208
     * Save an array of related options.
209
     *
210
     * @param  array  $options
211
     * @return array
212
     */
213
    protected function saveRelatedOptions(array $options)
214
    {
215
        foreach ($options as &$option) {
216
            $model = $option['id'] !== null
217
                ? Option::firstOrNew(['id' => $option['id']])
218
                : new Option;
219
220
            $model->product_id = $this->id;
221
            $model->fill($option);
222
            $model->save();
223
224
            $option['id'] = $model->id;
225
226
            if (is_array($option['values'])) {
227
                $this->saveRelatedOptionValues($model, $option['values']);
228
            }
229
        }
230
231
        return $options;
232
    }
233
234
    /**
235
     * Save related option values.
236
     *
237
     * @param  Option $option
238
     * @param  array  $values
239
     * @return void
240
     */
241
    protected function saveRelatedOptionValues(Option $option, array $values)
242
    {
243
        foreach ($values as $value) {
244
            $model = $value['id'] !== null
245
                ? OptionValue::firstOrNew(['id' => $value['id']])
246
                : new OptionValue;
247
248
            $model->option_id = $option->id;
249
            $model->fill($value);
250
            $model->save();
251
        }
252
    }
253
254
    /**
255
     * Left joins a subquery containing the product price.
256
     *
257
     * @param  \October\Rain\Database\Builder   $query
258
     * @return \October\Rain\Database\Builder
259
     */
260
    public function scopeJoinPrice(Builder $query)
261
    {
262
        $alias = 'prices';
263
        $grammar = $query->getQuery()->getGrammar();
264
265
        $subquery = Price::isActive()
266
            ->addselect('bedard_shop_prices.product_id')
267
            ->selectRaw('MIN('.$grammar->wrap('bedard_shop_prices.price').') as '.$grammar->wrap('price'))
268
            ->groupBy('bedard_shop_prices.product_id');
269
270
        return $query
271
            ->addSelect($alias.'.price')
272
            ->joinSubquery($subquery, $alias, 'bedard_shop_products.id', '=', $alias.'.product_id');
273
    }
274
275
    /**
276
     * Sync the inherited categories of all products.
277
     *
278
     * @return void
279
     */
280
    public static function syncAllInheritedCategories()
281
    {
282
        Queue::push(function ($job) {
283
            $data = [];
284
            $products = Product::with('categories')->get();
285
            $categoryTree = Category::getParentCategoryIds();
286
287
            foreach ($products as $product) {
288
                $inheritedCategoryIds = [];
289
                foreach ($product->categories as $category) {
290
                    if (array_key_exists($category->id, $categoryTree)) {
291
                        $inheritedCategoryIds = array_merge($inheritedCategoryIds, $categoryTree[$category->id]);
292
                    }
293
                }
294
295
                foreach (array_unique($inheritedCategoryIds) as $categoryId) {
296
                    $data[] = [
297
                        'category_id' => $categoryId,
298
                        'product_id' => $product->id,
299
                        'is_inherited' => 1,
300
                    ];
301
                }
302
            }
303
304
            DB::table('bedard_shop_category_product')->whereIsInherited(1)->delete();
305
            DB::table('bedard_shop_category_product')->insert($data);
306
            Discount::syncAllPrices();
307
308
            $job->delete();
309
        });
310
    }
311
312
    /**
313
     * Sync a product with it's inherited categories.
314
     *
315
     * @return void
316
     */
317
    public function syncInheritedCategories()
318
    {
319
        $data = [];
320
        $categoryIds = $this->categories()->lists('id');
321
        $parentIds = Category::isParentOf($categoryIds)->lists('id');
322
        foreach ($parentIds as $parentId) {
323
            $data[] = [
324
                'category_id' => $parentId,
325
                'product_id' => $this->id,
326
                'is_inherited' => true,
327
            ];
328
        }
329
330
        DB::table('bedard_shop_category_product')->whereProductId($this->id)->whereIsInherited(1)->delete();
331
        DB::table('bedard_shop_category_product')->insert($data);
332
333
        $categoryScope = array_merge($categoryIds, $parentIds);
334
        Discount::syncProductPrice($this, $categoryScope);
335
    }
336
337
    public function validateOptions()
338
    {
339
        if (! is_array($this->optionsInventories) ||
340
            ! is_array($this->optionsInventories['options'])) {
341
            return;
342
        }
343
344
        $names = [];
345
        foreach ($this->optionsInventories['options'] as $option) {
346
            $name = strtolower(trim($option['name']));
347
            // Validate the option
348
            $option = new Option($option);
349
            $option->validate();
350
351
            // Names must be unique
352
            if (in_array($name, $names)) {
353
                Flash::error(Lang::get('bedard.shop::lang.products.form.duplicate_options_error'));
354
                throw new ModelException($this);
355
            }
356
357
            $names[] = $name;
358
        }
359
    }
360
}
361