Completed
Push — master ( e123f6...ca26c3 )
by Scott
02:20
created

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