Current state

This commit is contained in:
2026-02-07 08:23:18 +01:00
commit 0a4372c40d
22479 changed files with 1553543 additions and 0 deletions

116
app/Models/Category.php Normal file
View File

@@ -0,0 +1,116 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\{BelongsTo, HasMany, BelongsToMany, HasOne};
use Illuminate\Database\Eloquent\SoftDeletes;
class Category extends Model
{
use SoftDeletes;
protected $fillable = [
'content_type_id','parent_id','name','slug',
'description','image','is_active','sort_order'
];
protected $casts = ['is_active' => 'boolean'];
/**
* Ensure slug is always lowercase and valid before saving.
*/
protected static function boot()
{
parent::boot();
static::saving(function (Category $model) {
if (isset($model->slug)) {
$model->slug = strtolower($model->slug);
if (!preg_match('/^[a-z0-9-]+$/', $model->slug)) {
throw new \InvalidArgumentException('Category slug must be lowercase and contain only a-z, 0-9, and dashes.');
}
}
});
}
public function contentType(): BelongsTo
{
return $this->belongsTo(ContentType::class);
}
public function parent(): BelongsTo
{
return $this->belongsTo(Category::class, 'parent_id');
}
public function children(): HasMany
{
return $this->hasMany(Category::class, 'parent_id')
->orderBy('sort_order')->orderBy('name');
}
public function descendants(): HasMany
{
return $this->children()->with('descendants');
}
public function seo(): HasOne
{
return $this->hasOne(CategorySeo::class);
}
public function artworks(): BelongsToMany
{
return $this->belongsToMany(Artwork::class, 'artwork_category');
}
public function scopeActive($query)
{
return $query->where('is_active', true);
}
public function scopeRoots($query)
{
return $query->whereNull('parent_id');
}
public function getFullSlugPathAttribute(): string
{
return $this->parent
? $this->parent->full_slug_path . '/' . $this->slug
: $this->slug;
}
/**
* Get the full public URL for this category (authoritative spec).
* Example: /photography/abstract/dark
*/
public function getUrlAttribute(): string
{
$contentTypeSlug = strtolower($this->contentType->slug);
$path = strtolower($this->full_slug_path);
return '/' . $contentTypeSlug . ($path ? '/' . $path : '');
}
/**
* Get the canonical URL for SEO (authoritative spec).
* Example: https://skinbase.org/photography/abstract/dark
*/
public function getCanonicalUrlAttribute(): string
{
return 'https://skinbase.org' . $this->url;
}
public function getBreadcrumbsAttribute(): array
{
return $this->parent
? array_merge($this->parent->breadcrumbs, [$this])
: [$this];
}
public function getRouteKeyName(): string
{
return 'slug';
}
}