Skip to content

Commit 11471ce

Browse files
committed
Fixed non-unique slugs
1 parent ef717fa commit 11471ce

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

src/HasSlug.php

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public static function bootHasSlug()
2323
if(!is_null($model->$attribute)) return;
2424

2525
$sluggable = $model->getSluggable();
26-
$model->attributes[$attribute] = str_slug($model->$sluggable);
26+
$model->attributes[$attribute] = $model->getUniqueSlug($model->$sluggable);
2727
}
2828
});
2929
}
@@ -42,13 +42,67 @@ public function translateSlugs($attribute)
4242

4343
foreach($this->getTranslatedLocales($this->getSluggable()) as $locale) {
4444
if(!isset($value[$locale]) || is_null($value[$locale])) {
45-
$value[$locale] = str_slug($this->getTranslation($sluggable, $locale));
45+
$value[$locale] = $this->getUniqueSlug($sluggable, $locale);
4646
}
4747
}
4848

4949
$this->attributes[$attribute] = json_encode($value);
5050
}
5151

52+
/**
53+
* Get a unique slug
54+
*
55+
* @param string $value
56+
* @param string|null $locale
57+
* @return string
58+
*/
59+
public function getUniqueSlug($sluggable, $locale = null)
60+
{
61+
if(!is_null($locale)) {
62+
$sluggable = $this->getTranslation($sluggable, $locale);
63+
}
64+
65+
$slug = str_slug($sluggable);
66+
67+
$i = 1;
68+
while($this->slugExists($slug, $locale)) {
69+
$slug = $slug . '-' . $i;
70+
}
71+
72+
return $slug;
73+
}
74+
75+
/**
76+
* Check if the slug exists (for the given locale if any)
77+
*
78+
* @param string $slug
79+
* @param string|null $locale
80+
* @return bool
81+
*/
82+
public function slugExists($slug, $locale = null)
83+
{
84+
$whereKey = is_null($locale) ? $this->getSlugStorageAttribute() : $this->getSlugStorageAttribute().'->'.$locale;
85+
86+
$query = static::where($whereKey, $slug)
87+
->withoutGlobalScopes();
88+
89+
if ($this->usesSoftDeletes()) {
90+
$query->withTrashed();
91+
}
92+
93+
return $query->exists();
94+
}
95+
96+
/**
97+
* Check if model uses soft deletes
98+
*
99+
* @return bool
100+
*/
101+
protected function usesSoftDeletes(): bool
102+
{
103+
return (bool) in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($this));
104+
}
105+
52106
/**
53107
* Get the attribute name used to generate the slug from
54108
*

tests/SluggableTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,15 @@ protected function getRouteBindingQuery($query) {
113113

114114
$this->assertSame('English custom query', $result ? $result->title : null);
115115
}
116+
117+
public function test_it_generates_unique_slugs()
118+
{
119+
$model = TestModel::create(['title' => 'My test title']);
120+
121+
$this->assertSame('my-test-title', $model->slug);
122+
123+
$model = TestModel::create(['title' => 'My test title']);
124+
125+
$this->assertNotEquals('my-test-title', $model->slug);
126+
}
116127
}

0 commit comments

Comments
 (0)