🆙 Add cms i using 🆙

This commit is contained in:
Remco
2025-11-25 22:42:56 +01:00
parent 94704e0925
commit d44196149e
35591 changed files with 3601123 additions and 0 deletions
@@ -0,0 +1,23 @@
<?php
namespace Spatie\Sluggable\Exceptions;
use Exception;
class InvalidOption extends Exception
{
public static function missingFromField(): static
{
return new static('Could not determine which fields should be sluggified');
}
public static function missingSlugField(): static
{
return new static('Could not determine in which field the slug should be saved');
}
public static function invalidMaximumLength(): static
{
return new static('Maximum length should be greater than zero');
}
}
@@ -0,0 +1,232 @@
<?php
namespace Spatie\Sluggable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Spatie\Sluggable\Exceptions\InvalidOption;
trait HasSlug
{
protected SlugOptions $slugOptions;
abstract public function getSlugOptions(): SlugOptions;
protected static function bootHasSlug(): void
{
static::creating(function (Model $model) {
$model->generateSlugOnCreate();
});
static::updating(function (Model $model) {
$model->generateSlugOnUpdate();
});
}
protected function generateSlugOnCreate(): void
{
$this->slugOptions = $this->getSlugOptions();
if ($this->slugOptions->skipGenerate) {
return;
}
if (! $this->slugOptions->generateSlugsOnCreate) {
return;
}
if ($this->slugOptions->preventOverwrite) {
if ($this->{$this->slugOptions->slugField} !== null) {
return;
}
}
$this->addSlug();
}
protected function generateSlugOnUpdate(): void
{
$this->slugOptions = $this->getSlugOptions();
if ($this->slugOptions->skipGenerate) {
return;
}
if (! $this->slugOptions->generateSlugsOnUpdate) {
return;
}
if ($this->slugOptions->preventOverwrite) {
if ($this->{$this->slugOptions->slugField} !== null) {
return;
}
}
$this->addSlug();
}
public function generateSlug(): void
{
$this->slugOptions = $this->getSlugOptions();
$this->addSlug();
}
protected function addSlug(): void
{
$this->ensureValidSlugOptions();
$slug = $this->generateNonUniqueSlug();
if ($this->slugOptions->generateUniqueSlugs) {
$slug = $this->makeSlugUnique($slug);
}
$slugField = $this->slugOptions->slugField;
$this->$slugField = $slug;
}
protected function generateNonUniqueSlug(): string
{
$slugField = $this->slugOptions->slugField;
if ($this->hasCustomSlugBeenUsed() && ! empty($this->$slugField)) {
return $this->$slugField;
}
return Str::slug($this->getSlugSourceString(), $this->slugOptions->slugSeparator, $this->slugOptions->slugLanguage);
}
protected function hasCustomSlugBeenUsed(): bool
{
$slugField = $this->slugOptions->slugField;
return $this->getOriginal($slugField) != $this->$slugField;
}
protected function getSlugSourceString(): string
{
if (is_callable($this->slugOptions->generateSlugFrom)) {
$slugSourceString = $this->getSlugSourceStringFromCallable();
return $this->generateSubstring($slugSourceString);
}
$slugSourceString = collect($this->slugOptions->generateSlugFrom)
->map(fn (string $fieldName): string => data_get($this, $fieldName, ''))
->implode($this->slugOptions->slugSeparator);
return $this->generateSubstring($slugSourceString);
}
protected function getSlugSourceStringFromCallable(): string
{
return call_user_func($this->slugOptions->generateSlugFrom, $this);
}
protected function makeSlugUnique(string $slug): string
{
$originalSlug = $slug;
$iteration = 0;
while (
$slug === '' ||
$this->otherRecordExistsWithSlug($slug) ||
($this->slugOptions->useSuffixOnFirstOccurrence && $iteration === 0)
) {
$suffix = $this->generateSuffix($originalSlug, $iteration++);
$slug = $originalSlug . $this->slugOptions->slugSeparator . $suffix;
}
return $slug;
}
protected function generateSuffix(string $originalSlug, int $iteration): string
{
if ($this->slugOptions->suffixGenerator) {
return call_user_func($this->slugOptions->suffixGenerator, $originalSlug, $iteration);
}
return strval($this->slugOptions->startSlugSuffixFrom + $iteration);
}
protected function otherRecordExistsWithSlug(string $slug): bool
{
$query = static::where($this->slugOptions->slugField, $slug)
->withoutGlobalScopes();
if ($this->slugOptions->extraScopeCallback) {
$query->where($this->slugOptions->extraScopeCallback);
}
if ($this->exists) {
$query->where($this->getKeyName(), '!=', $this->getKey());
}
if ($this->usesSoftDeletes()) {
$query->withTrashed();
}
return $query->exists();
}
protected function usesSoftDeletes(): bool
{
return in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($this), true);
}
protected function ensureValidSlugOptions(): void
{
if (is_array($this->slugOptions->generateSlugFrom) && ! count($this->slugOptions->generateSlugFrom)) {
throw InvalidOption::missingFromField();
}
if (! strlen($this->slugOptions->slugField)) {
throw InvalidOption::missingSlugField();
}
if ($this->slugOptions->maximumLength <= 0) {
throw InvalidOption::invalidMaximumLength();
}
}
/**
* Helper function to handle multi-bytes strings if the module mb_substr is present,
* default to substr otherwise.
*/
protected function generateSubstring($slugSourceString)
{
if (function_exists('mb_substr')) {
return mb_substr($slugSourceString, 0, $this->slugOptions->maximumLength);
}
return substr($slugSourceString, 0, $this->slugOptions->maximumLength);
}
public static function findBySlug(string $slug, array $columns = ['*'], ?callable $additionalQuery = null)
{
$modelInstance = new static();
$field = $modelInstance->getSlugOptions()->slugField;
$query = static::query();
if (in_array(HasTranslatableSlug::class, class_uses_recursive(static::class))) {
$currentLocale = $modelInstance->getLocale();
$fallbackLocale = config('app.fallback_locale');
$currentField = "{$field}->{$currentLocale}";
$fallbackField = "{$field}->{$fallbackLocale}";
$query->where(fn ($query) => $query->where($currentField, $slug)->orWhere($fallbackField, $slug));
} else {
$query->where($field, $slug);
}
if (is_callable($additionalQuery)) {
$additionalQuery($query);
}
return $query->first($columns);
}
}
@@ -0,0 +1,141 @@
<?php
namespace Spatie\Sluggable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Localizable;
trait HasTranslatableSlug
{
use HasSlug;
use Localizable;
protected function getLocalesForSlug(): Collection
{
$generateSlugFrom = $this->slugOptions->generateSlugFrom;
if (is_callable($generateSlugFrom)) {
// returns a collection of locales that were given to the SlugOptions object
// when it was instantiated with the 'createWithLocales' method.
return Collection::make($this->slugOptions->translatableLocales);
}
// collects all locales for all translatable fields
return Collection::wrap($generateSlugFrom)
->filter(fn ($fieldName) => $this->isTranslatableAttribute($fieldName))
->flatMap(fn ($fieldName) => $this->getTranslatedLocales($fieldName));
}
protected function addSlug(): void
{
$this->ensureValidSlugOptions();
$this->getLocalesForSlug()->unique()->each(function ($locale) {
$this->withLocale($locale, function () use ($locale) {
$slug = $this->generateNonUniqueSlug();
$slugField = $this->slugOptions->slugField;
if ($this->slugOptions->generateUniqueSlugs) {
// temporarly change the 'slugField' of the SlugOptions
// so the 'otherRecordExistsWithSlug' method queries
// the locale JSON column instead of the 'slugField'.
$this->slugOptions->saveSlugsTo("{$slugField}->{$locale}");
$slug = $this->makeSlugUnique($slug);
// revert the change for the next iteration
$this->slugOptions->saveSlugsTo($slugField);
}
$this->setTranslation($slugField, $locale, $slug);
});
});
}
protected function generateNonUniqueSlug(): string
{
$slugField = $this->slugOptions->slugField;
$slugString = $this->getSlugSourceString();
$slug = $this->getTranslations($slugField)[$this->getLocale()] ?? null;
$slugGeneratedFromCallable = is_callable($this->slugOptions->generateSlugFrom);
$hasCustomSlug = $this->hasCustomSlugBeenUsed() && ! empty($slug);
$hasNonChangedCustomSlug = ! $slugGeneratedFromCallable && ! empty($slug) && ! $this->slugIsBasedOnTitle();
if ($hasCustomSlug || $hasNonChangedCustomSlug) {
$slugString = $slug;
}
return Str::slug($slugString, $this->slugOptions->slugSeparator, $this->slugOptions->slugLanguage);
}
protected function getSlugSourceStringFromCallable(): string
{
return call_user_func($this->slugOptions->generateSlugFrom, $this, $this->getLocale());
}
protected function slugIsBasedOnTitle(): bool
{
$slugField = $this->slugOptions->slugField;
$titleSlug = Str::slug($this->getOriginalSourceString(), $this->slugOptions->slugSeparator, $this->slugOptions->slugLanguage);
$currentSlug = $this->getTranslations($slugField)[$this->getLocale()] ?? null;
if (! str_starts_with($currentSlug, $titleSlug) || $titleSlug === '') {
return false;
}
if ($titleSlug === $currentSlug) {
return true;
}
$slugSeparator = $currentSlug[strlen($titleSlug)];
$slugIdentifier = substr($currentSlug, strlen($titleSlug) + 1);
return $slugSeparator === $this->slugOptions->slugSeparator && is_numeric($slugIdentifier);
}
protected function getOriginalSourceString(): string
{
if (is_callable($this->slugOptions->generateSlugFrom)) {
$slugSourceString = $this->getSlugSourceStringFromCallable();
return $this->generateSubstring($slugSourceString);
}
$slugSourceString = collect($this->slugOptions->generateSlugFrom)
->map(fn (string $fieldName): string => $this->getOriginal($fieldName)[$this->getLocale()] ?? '')
->implode($this->slugOptions->slugSeparator);
return $this->generateSubstring($slugSourceString);
}
protected function hasCustomSlugBeenUsed(): bool
{
$slugField = $this->slugOptions->slugField;
$originalSlug = $this->getOriginal($slugField)[$this->getLocale()] ?? null;
$newSlug = $this->getTranslations($slugField)[$this->getLocale()] ?? null;
return $originalSlug !== $newSlug;
}
public function resolveRouteBindingQuery($query, $value, $field = null): Builder|Relation
{
$field = $field ?? $this->getRouteKeyName();
$slug = $this->getSlugOptions()->slugField;
if (str_contains($field, '.') && str_ends_with($field, ".{$slug}")) {
return $query->where("{$field}->{$this->getLocale()}", $value);
}
if ($field === $slug) {
return $query->where("{$field}->{$this->getLocale()}", $value);
}
return parent::resolveRouteBindingQuery($query, $value, $field);
}
}
@@ -0,0 +1,159 @@
<?php
namespace Spatie\Sluggable;
class SlugOptions
{
/** @var array|callable */
public $generateSlugFrom;
/** @var callable */
public $extraScopeCallback;
/** @var (callable(string, int): string)|null */
public $suffixGenerator;
public string $slugField;
public bool $generateUniqueSlugs = true;
public int $maximumLength = 250;
public bool $skipGenerate = false;
public bool $generateSlugsOnCreate = true;
public bool $generateSlugsOnUpdate = true;
public bool $preventOverwrite = false;
public string $slugSeparator = '-';
public string $slugLanguage = 'en';
public array $translatableLocales = [];
public int $startSlugSuffixFrom = 1;
public bool $useSuffixOnFirstOccurrence = false;
public static function create(): static
{
return new static();
}
public static function createWithLocales(array $locales): static
{
$slugOptions = static::create();
$slugOptions->translatableLocales = $locales;
return $slugOptions;
}
public function generateSlugsFrom(string | array | callable $fieldName): self
{
if (is_string($fieldName)) {
$fieldName = [$fieldName];
}
$this->generateSlugFrom = $fieldName;
return $this;
}
public function saveSlugsTo(string $fieldName): self
{
$this->slugField = $fieldName;
return $this;
}
public function allowDuplicateSlugs(): self
{
$this->generateUniqueSlugs = false;
return $this;
}
public function slugsShouldBeNoLongerThan(int $maximumLength): self
{
$this->maximumLength = $maximumLength;
return $this;
}
public function skipGenerateWhen(callable $callable): self
{
$this->skipGenerate = $callable() === true;
return $this;
}
public function doNotGenerateSlugsOnCreate(): self
{
$this->generateSlugsOnCreate = false;
return $this;
}
public function doNotGenerateSlugsOnUpdate(): self
{
$this->generateSlugsOnUpdate = false;
return $this;
}
public function preventOverwrite(): self
{
$this->preventOverwrite = true;
return $this;
}
public function usingSeparator(string $separator): self
{
$this->slugSeparator = $separator;
return $this;
}
public function usingLanguage(string $language): self
{
$this->slugLanguage = $language;
return $this;
}
public function extraScope(callable $callbackMethod): self
{
$this->extraScopeCallback = $callbackMethod;
return $this;
}
public function startSlugSuffixFrom(int $startSlugSuffixFrom): self
{
$this->startSlugSuffixFrom = max(1, $startSlugSuffixFrom);
return $this;
}
public function useSuffixOnFirstOccurrence(): self
{
$this->useSuffixOnFirstOccurrence = true;
return $this;
}
/**
* @param callable(string $slug, int $iteration): string $generator
*/
public function usingSuffixGenerator(callable $generator): self
{
$this->suffixGenerator = $generator;
return $this;
}
}