Skip to content

Commit 0cd4ff1

Browse files
committed
Added tagged
1 parent 32aff4a commit 0cd4ff1

File tree

8 files changed

+786
-2
lines changed

8 files changed

+786
-2
lines changed

app/Models/Concerns/Taggable.php

+298
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
<?php
2+
3+
namespace App\Models\Concerns;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use App\Models\Tag;
7+
use Illuminate\Support\Str;
8+
9+
trait Taggable
10+
{
11+
/**
12+
* The tags delimiter.
13+
*
14+
* @var string
15+
*/
16+
protected static $delimiter = ',';
17+
18+
/**
19+
* The Eloquent tags model name.
20+
*
21+
* @var string
22+
*/
23+
protected static $tagsModel = Tag::class;
24+
25+
/**
26+
* @return string
27+
*/
28+
public static function getTagsDelimiter()
29+
{
30+
return static::$delimiter;
31+
}
32+
33+
/**
34+
* @param string $delimiter
35+
*
36+
* @return string
37+
*/
38+
public static function setTagsDelimiter(string $delimiter)
39+
{
40+
static::$delimiter = $delimiter;
41+
42+
return static::class;
43+
}
44+
45+
/**
46+
* @return string
47+
*/
48+
public static function getTagsModel()
49+
{
50+
return static::$tagsModel;
51+
}
52+
53+
/**
54+
* @param string $model
55+
*/
56+
public static function setTagsModel(string $model)
57+
{
58+
static::$tagsModel = $model;
59+
}
60+
61+
/**
62+
* @return mixed
63+
*/
64+
public function tags()
65+
{
66+
return $this->morphToMany(static::$tagsModel, 'taggable', 'tagged', 'taggable_id', 'tag_id');
67+
}
68+
69+
/**
70+
* @return mixed
71+
*/
72+
public static function allTags()
73+
{
74+
$instance = new static();
75+
76+
return $instance->createTagsModel()->whereNamespace(
77+
$instance->getEntityClassName()
78+
);
79+
}
80+
81+
/**
82+
* @param \Illuminate\Database\Eloquent\Builder $query
83+
* @param mixed $tags
84+
* @param string $type
85+
*
86+
* @return \Illuminate\Database\Eloquent\Builder
87+
*/
88+
public static function scopeWhereTag(Builder $query, $tags, $type = 'slug')
89+
{
90+
$tags = (new static())->prepareTags($tags);
91+
92+
foreach ($tags as $tag) {
93+
$query->whereHas('tags', function ($query) use ($type, $tag) {
94+
$query->where($type, $tag);
95+
});
96+
}
97+
98+
return $query;
99+
}
100+
101+
/**
102+
* @param \Illuminate\Database\Eloquent\Builder $query
103+
* @param mixed $tags
104+
* @param string $type
105+
*
106+
* @return \Illuminate\Database\Eloquent\Builder
107+
*/
108+
public static function scopeWithTag(Builder $query, $tags, $type = 'slug')
109+
{
110+
$tags = (new static())->prepareTags($tags);
111+
112+
return $query->whereHas('tags', function ($query) use ($type, $tags) {
113+
$query->whereIn($type, $tags);
114+
});
115+
}
116+
117+
/**
118+
* @param \Illuminate\Database\Eloquent\Builder $query
119+
* @param mixed $tags
120+
* @param string $type
121+
*
122+
* @return \Illuminate\Database\Eloquent\Builder
123+
*/
124+
public static function scopeWithoutTag(Builder $query, $tags, $type = 'slug')
125+
{
126+
$tags = (new static())->prepareTags($tags);
127+
128+
return $query->whereDoesntHave('tags', function ($query) use ($type, $tags) {
129+
$query->whereIn($type, $tags);
130+
});
131+
}
132+
133+
/**
134+
* @param mixed $tags
135+
*
136+
* @return bool
137+
*/
138+
public function tag($tags)
139+
{
140+
foreach ($this->prepareTags($tags) as $tag) {
141+
$this->addTag($tag);
142+
}
143+
144+
return true;
145+
}
146+
147+
/**
148+
* @param mixed|null $tags
149+
*
150+
* @return bool
151+
*/
152+
public function untag($tags = null)
153+
{
154+
$tags = $tags ?: $this->tags->pluck('name')->all();
155+
156+
foreach ($this->prepareTags($tags) as $tag) {
157+
$this->removeTag($tag);
158+
}
159+
160+
return true;
161+
}
162+
163+
/**
164+
* @param mixed $tags
165+
* @param string $type
166+
*
167+
* @return bool
168+
*/
169+
public function setTags($tags, $type = 'name')
170+
{
171+
// Prepare the tags
172+
$tags = $this->prepareTags($tags);
173+
174+
// Get the current entity tags
175+
$entityTags = $this->tags->pluck($type)->all();
176+
177+
// Prepare the tags to be added and removed
178+
$tagsToAdd = array_diff($tags, $entityTags);
179+
$tagsToDel = array_diff($entityTags, $tags);
180+
181+
// Detach the tags
182+
if (!empty($tagsToDel)) {
183+
$this->untag($tagsToDel);
184+
}
185+
186+
// Attach the tags
187+
if (!empty($tagsToAdd)) {
188+
$this->tag($tagsToAdd);
189+
}
190+
191+
return true;
192+
}
193+
194+
/**
195+
* @param string $name
196+
*/
197+
public function addTag(string $name)
198+
{
199+
$tag = self::createTagsModel()->firstOrNew([
200+
'slug' => $this->generateTagSlug($name),
201+
'namespace' => $this->getEntityClassName(),
202+
]);
203+
204+
if (!$tag->exists) {
205+
$tag->name = $name;
206+
207+
$tag->save();
208+
}
209+
210+
if (!$this->tags()->get()->contains($tag->id)) {
211+
$tag->update(['count' => $tag->count + 1]);
212+
213+
$this->tags()->attach($tag);
214+
}
215+
216+
$this->load('tags');
217+
}
218+
219+
/**
220+
* @param string $name
221+
*/
222+
public function removeTag(string $name)
223+
{
224+
$slug = $this->generateTagSlug($name);
225+
226+
$namespace = $this->getEntityClassName();
227+
228+
$tag = self
229+
::createTagsModel()
230+
->whereNamespace($namespace)
231+
->where(function ($query) use ($name, $slug) {
232+
$query
233+
->orWhere('name', '=', $name)
234+
->orWhere('slug', '=', $slug);
235+
})
236+
->first();
237+
238+
if ($tag && $this->tags()->get()->contains($tag->id)) {
239+
$tag->update(['count' => $tag->count - 1]);
240+
241+
$this->tags()->detach($tag);
242+
}
243+
244+
$this->load('tags');
245+
}
246+
247+
/**
248+
* @param mixed $tags
249+
*
250+
* @return array
251+
*/
252+
public function prepareTags($tags): array
253+
{
254+
if (is_null($tags)) {
255+
return [];
256+
}
257+
258+
if (is_string($tags)) {
259+
$delimiter = preg_quote(self::getTagsDelimiter(), '#');
260+
261+
$tags = array_map('trim',
262+
preg_split("#[{$delimiter}]#", $tags)
263+
);
264+
}
265+
266+
return array_unique(array_filter($tags));
267+
}
268+
269+
/**
270+
* @return mixed
271+
*/
272+
public static function createTagsModel()
273+
{
274+
return new static::$tagsModel();
275+
}
276+
277+
/**
278+
* Generate the tag slug using the given name.
279+
*
280+
* @param string $name
281+
*
282+
* @return string
283+
*/
284+
protected function generateTagSlug(string $name): string
285+
{
286+
return Str::of($name)->slug()->toString();
287+
}
288+
289+
/**
290+
* Returns the entity class name.
291+
*
292+
* @return string
293+
*/
294+
protected function getEntityClassName(): string
295+
{
296+
return $this->tags()->getMorphClass();
297+
}
298+
}

0 commit comments

Comments
 (0)