Completed
Push — master ( 289dbc...0efbaa )
by Scott
02:23
created

Category::applyCustomOrder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 5
nc 2
nop 1
1
<?php namespace Bedard\Shop\Models;
2
3
use Bedard\Shop\Classes\ProductsQuery;
4
use Lang;
5
use Model;
6
use October\Rain\Database\Builder;
7
8
/**
9
 * Category Model.
10
 */
11
class Category extends Model
12
{
13
    use \October\Rain\Database\Traits\Purgeable,
14
        \October\Rain\Database\Traits\Validation;
15
16
    /**
17
     * @var string The database table used by the model.
18
     */
19
    public $table = 'bedard_shop_categories';
20
21
    /**
22
     * @var array Default attributes
23
     */
24
    public $attributes = [
25
        'description_html' => '',
26
        'description_plain' => '',
27
        'product_columns' => 4,
28
        'product_order' => '',
29
        'product_rows' => 3,
30
        'product_sort' => 'created_at:desc',
31
    ];
32
33
    /**
34
     * @var array Attribute casting
35
     */
36
    protected $casts = [
37
        'is_active' => 'boolean',
38
        'is_visible' => 'boolean',
39
    ];
40
41
    /**
42
     * @var array Guarded fields
43
     */
44
    protected $guarded = ['*'];
45
46
    /**
47
     * @var array Jsonable fields
48
     */
49
    protected $jsonable = [
50
        'product_order',
51
    ];
52
53
    /**
54
     * @var array Fillable fields
55
     */
56
    protected $fillable = [
57
        'is_active',
58
        'is_visible',
59
        'category_filters',
60
        'description_html',
61
        'description_plain',
62
        'name',
63
        'product_columns',
64
        'parent_id',
65
        'product_order',
66
        'product_rows',
67
        'product_sort',
68
        'product_sort_column',
69
        'product_sort_direction',
70
        'slug',
71
    ];
72
73
    /**
74
     * @var array Purgeable fields
75
     */
76
    protected $purgeable = [
77
        'category_filters',
78
        'filters_list',
79
        'product_sort',
80
    ];
81
82
    /**
83
     * @var array Relations
84
     */
85
    public $attachMany = [
86
        'thumbnails' => 'System\Models\File',
87
    ];
88
89
    public $belongsTo = [
90
        'parent' => 'Bedard\Shop\Models\Category',
91
    ];
92
93
    public $belongsToMany = [
94
        'discounts' => [
95
            'Bedard\Shop\Models\Discount',
96
            'table' => 'bedard_shop_category_discount',
97
        ],
98
        'products' => [
99
            'Bedard\Shop\Models\Product',
100
            'table' => 'bedard_shop_category_product',
101
            'pivot' => ['is_inherited'],
102
        ],
103
    ];
104
105
    public $hasMany = [
106
        'filters' => [
107
            'Bedard\Shop\Models\Filter',
108
        ],
109
    ];
110
111
    /**
112
     * @var array Validation
113
     */
114
    public $rules = [
115
        'name' => 'required',
116
        'product_columns' => 'required|integer|min:1',
117
        'product_rows' => 'required|integer|min:0',
118
        'slug' => 'required|unique:bedard_shop_categories',
119
    ];
120
121
    /**
122
     * After delete.
123
     *
124
     * @return void
125
     */
126
    public function afterDelete()
127
    {
128
        $this->syncInheritedProductsAfterDelete();
129
    }
130
131
    /**
132
     * After save.
133
     *
134
     * @return void
135
     */
136
    public function afterSave()
137
    {
138
        $this->saveFiltersRelationship();
139
        $this->syncInheritedProductsAfterSave();
140
    }
141
142
    /**
143
     * Before save.
144
     *
145
     * @return void
146
     */
147
    public function beforeSave()
148
    {
149
        $this->setPlainDescription();
150
        $this->setNullParentId();
151
        $this->setProductSort();
152
    }
153
154
    /**
155
     * Delete filters that have the is_deleted flag.
156
     *
157
     * @param  array $filters
158
     * @return array
159
     */
160
    protected function deleteRelatedFilters(array $filters)
161
    {
162
        return array_filter($filters, function ($filter) {
163
            if ($filter['id'] !== null && $filter['is_deleted']) {
164
                Filter::find($filter['id'])->delete();
165
            }
166
167
            return ! $filter['is_deleted'];
168
        });
169
    }
170
171
    /**
172
     * Get the parent IDs of every category.
173
     *
174
     * @return array
175
     */
176
    public static function getParentCategoryIds()
177
    {
178
        $categories = self::select('id', 'parent_id')->get();
179
180
        $tree = [];
181
        foreach ($categories as $category) {
182
            $tree[$category->id] = self::walkParentCategories($categories, $category);
183
        }
184
185
        return $tree;
186
    }
187
188
    /**
189
     * Find the child ids of a parent category.
190
     *
191
     * @param  \Bedard\Shop\Models\Category|int     $parent
192
     * @param  \October\Rain\Database\Collection    $categories
193
     * @return array
194
     */
195
    public static function getChildIds($parent, \October\Rain\Database\Collection $categories = null)
196
    {
197
        if ($categories === null) {
198
            $categories = self::whereNotNull('parent_id')
199
                ->where('id', '<>', $parent)
200
                ->select('id', 'parent_id')
201
                ->get();
202
        }
203
204
        $children = [];
205
        foreach ($categories as $category) {
206
            if ($category->parent_id == $parent) {
207
                $children[] = $category->id;
208
                $children = array_merge($children, self::getChildIds($category->id, $categories));
209
            }
210
        }
211
212
        return $children;
213
    }
214
215
    public static function getParentIds($children, \October\Rain\Database\Collection $categories = null)
216
    {
217
        if (! is_array($children)) {
218
            $children = [$children];
219
        }
220
221
        if ($categories === null) {
222
            $categories = self::select('id', 'parent_id')->get();
223
        }
224
225
        $parents = [];
226
        foreach ($children as $child) {
227
            $category = $categories->filter(function ($model) use ($child) {
228
                return $model->id == $child;
229
            })->first();
230
231
            while ($category && $category->parent_id) {
232
                $parents[] = $category->parent_id;
233
                $category = $categories->filter(function ($model) use ($category) {
234
                    return $model->id == $category->parent_id;
235
                })->first();
236
            }
237
        }
238
239
        return array_unique($parents);
240
    }
241
242
    /**
243
     * Get options for the parent form field.
244
     *
245
     * @return array
246
     */
247
    public function getParentIdOptions()
248
    {
249
        $options = $this->exists
250
            ? self::where('id', '<>', $this->id)->isNotChildOf($this->id)->orderBy('name')->lists('name', 'id')
251
            : self::orderBy('name')->lists('name', 'id');
252
253
        array_unshift($options, '<em>'.Lang::get('bedard.shop::lang.categories.form.no_parent').'</em>');
254
255
        return $options;
256
    }
257
258
    /**
259
     * Query a category's products.
260
     *
261
     * @param  array $params
262
     * @return \October\Rain\Database\Collection
263
     */
264
    public function getProducts(array $params = [])
265
    {
266
        $query = new ProductsQuery($this, $params);
267
268
        return $query->get();
269
    }
270
271
    /**
272
     * Determine if the category has a custom sort.
273
     *
274
     * @return bool
275
     */
276
    public function isCustomSorted()
277
    {
278
        return ! $this->product_sort_column && ! $this->product_sort_direction;
279
    }
280
281
    /**
282
     * Determine if the category is filtered.
283
     *
284
     * @return bool
285
     */
286
    public function isFiltered()
287
    {
288
        return $this->filters->count() > 0;
289
    }
290
291
    /**
292
     * Determine if the category is paginated.
293
     *
294
     * @return bool
295
     */
296
    public function isPaginated()
297
    {
298
        return $this->product_rows > 0;
299
    }
300
301
    /**
302
     * Save filters relationship.
303
     *
304
     * @return void
305
     */
306
    protected function saveFiltersRelationship()
307
    {
308
        $filters = $this->getOriginalPurgeValue('category_filters');
309
310
        if (is_array($filters) && ! empty($filters)) {
311
            $filters = $this->deleteRelatedFilters($filters);
312
            $this->saveRelatedFilters($filters);
313
        }
314
    }
315
316
    /**
317
     * Save related filters.
318
     *
319
     * @param  array  $filters
320
     * @return void
321
     */
322
    protected function saveRelatedFilters(array $filters)
323
    {
324
        foreach ($filters as $filter) {
325
            $model = $filter['id'] !== null
326
                ? Filter::firstOrNew(['id' => $filter['id']])
327
                : new Filter;
328
329
            $filter['category_id'] = $this->id;
330
            $model->fill($filter);
331
            $model->save();
332
        }
333
    }
334
335
    /**
336
     * Query categories that are listed as active.
337
     *
338
     * @param  \October\Rain\Database\Builder   $query
339
     * @return \October\Rain\Database\Builder
340
     */
341
    public function scopeIsActive($query)
342
    {
343
        return $query->whereIsActive(true);
344
    }
345
346
    /**
347
     * Query categories that are listed as not active.
348
     *
349
     * @param  \October\Rain\Database\Builder   $query
350
     * @return \October\Rain\Database\Builder
351
     */
352
    public function scopeIsNotActive($query)
353
    {
354
        return $query->whereIsActive(false);
355
    }
356
357
    /**
358
     * Query categories that are children of another category.
359
     *
360
     * @param  \October\Rain\Database\Builder   $query
361
     * @param  \Bedard\Shop\Models\Category|int $parent
362
     * @return \October\Rain\Database\Builder
363
     */
364
    public function scopeIsChildOf($query, $parent)
365
    {
366
        return $query->whereIn('id', self::getChildIds($parent));
367
    }
368
369
    /**
370
     * Query categories that are not children of another category.
371
     *
372
     * @param  \October\Rain\Database\Builder   $query
373
     * @param  \Bedard\Shop\Models\Category|int $parent
374
     * @return \October\Rain\Database\Builder
375
     */
376
    public function scopeIsNotChildOf($query, $parent)
377
    {
378
        return $query->whereNotIn('id', self::getChildIds($parent));
379
    }
380
381
    /**
382
     * Query categories that are a parent of another category.
383
     *
384
     * @param  \October\Rain\Database\Builder   $query
385
     * @param  int                              $child
386
     * @return \October\Rain\Database\Builder
387
     */
388
    public function scopeIsParentOf($query, $child)
389
    {
390
        return $query->whereIn('id', self::getParentIds($child));
391
    }
392
393
    /**
394
     * Query categories that are not a parent of another category.
395
     *
396
     * @param  \October\Rain\Database\Builder   $query
397
     * @param  int                              $child
398
     * @return \October\Rain\Database\Builder
399
     */
400
    public function scopeIsNotParentOf($query, $child)
401
    {
402
        return $query->whereNotIn('id', self::getParentIds($child));
403
    }
404
405
    /**
406
     * Convert falsey parent id values to null.
407
     *
408
     * @return void
409
     */
410
    protected function setNullParentId()
411
    {
412
        if (! $this->parent_id) {
413
            $this->parent_id = null;
414
        }
415
    }
416
417
    /**
418
     * Set the plain text description_html.
419
     *
420
     * @return void
421
     */
422
    protected function setPlainDescription()
423
    {
424
        $this->description_plain = strip_tags($this->description_html);
425
    }
426
427
    /**
428
     * Set the product sort.
429
     *
430
     * @return void
431
     */
432
    protected function setProductSort()
433
    {
434
        $sort = $this->getOriginalPurgeValue('product_sort');
435
436
        if ($sort === 'custom') {
437
            $this->product_sort_column = null;
438
            $this->product_sort_direction = null;
439
        } else {
440
            $parts = explode(':', $this->getOriginalPurgeValue('product_sort'));
441
            $this->product_sort_column = $parts[0];
442
            $this->product_sort_direction = $parts[1];
443
        }
444
    }
445
446
    /**
447
     * If the model wasn't deleted in bulk, sync inherited categories.
448
     *
449
     * @return void
450
     */
451
    public function syncInheritedProductsAfterDelete()
452
    {
453
        if (! $this->dontSyncAfterDelete) {
454
            Product::syncAllInheritedCategories();
455
        }
456
    }
457
458
    /**
459
     * If the parent id has changed, resync all products.
460
     *
461
     * @return void
462
     */
463
    public function syncInheritedProductsAfterSave()
464
    {
465
        if ($this->isDirty('parent_id')) {
466
            Product::syncAllInheritedCategories();
467
        }
468
    }
469
470
    /**
471
     * Itterate over categories and update them with the given values.
472
     *
473
     * @param  array    $categories
474
     * @return void
475
     */
476
    public static function updateMany(array $categories)
477
    {
478
        foreach ($categories as $category) {
479
            $id = $category['id'];
480
            unset($category['id']);
481
            self::whereId($id)->update($category);
482
        }
483
    }
484
485
    /**
486
     * Walk up the category tree gathering parent IDs.
487
     *
488
     * @param  \October\Rain\Database\Collection    $categories     All categories
489
     * @param  \Bedard\Shop\Models\Category         $category       Current category being walked over
490
     * @param  array                                $tree           Tree of parent IDs
491
     * @return arrau
492
     */
493
    public static function walkParentCategories($categories, $category, $tree = [])
494
    {
495
        if ($category && $category->parent_id !== null) {
496
            $tree[] = $category->parent_id;
497
            $tree = array_merge($tree, self::walkParentCategories($categories, $categories->find($category->parent_id)));
498
        }
499
500
        return $tree;
501
    }
502
}
503