Passed
Pull Request — 2.x (#729)
by Antonio Carlos
05:59
created

HasSlug::getSlugModelClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.5

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 9
ccs 2
cts 4
cp 0.5
crap 2.5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace A17\Twill\Models\Behaviors;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Support\Facades\DB;
7
8
9
trait HasSlug
10
{
11
    private $nb_variation_slug = 3;
12
13 33
    protected static function bootHasSlug()
14
    {
15
        static::created(function ($model) {
16 21
            $model->setSlugs();
17 33
        });
18
19
        static::updated(function ($model) {
20 13
            $model->setSlugs();
21 33
        });
22
23
        static::restored(function ($model) {
24 1
            $model->setSlugs($restoring = true);
25 33
        });
26 33
    }
27
28 4
    public function slugs()
29
    {
30 4
        return $this->hasMany($this->getSlugModelClass());
0 ignored issues
show
Bug introduced by
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

30
        return $this->/** @scrutinizer ignore-call */ hasMany($this->getSlugModelClass());
Loading history...
31 4
    }
32
33
    public function getSlugClass()
34
    {
35
        return new $this->getSlugModelClass();
36
    }
37
38
    public function getSlugModelClass()
39
    {
40
        $slug = config('twill.namespace') . "\Models\Slugs\\" . $this->getSlugClassName();
41 4
42
        if (@class_exists($slug)) {
43 4
            return $slug;
44
        }
45
46
        return $this->getCapsuleSlugClass(class_basename($this));
0 ignored issues
show
Bug introduced by
It seems like getCapsuleSlugClass() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

46
        return $this->/** @scrutinizer ignore-call */ getCapsuleSlugClass(class_basename($this));
Loading history...
47
    }
48
49
    protected function getSlugClassName()
50
    {
51
        return class_basename($this) . "Slug";
52
    }
53
54
    public function scopeForSlug($query, $slug)
55
    {
56
        return $query->whereHas('slugs', function ($query) use ($slug) {
57
            $query->whereSlug($slug);
58
            $query->whereActive(true);
59
            $query->whereLocale(app()->getLocale());
0 ignored issues
show
introduced by
The method getLocale() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

59
            $query->whereLocale(app()->/** @scrutinizer ignore-call */ getLocale());
Loading history...
60
        })->with(['slugs']);
61
    }
62
63
    public function scopeForInactiveSlug($query, $slug)
64
    {
65
        return $query->whereHas('slugs', function ($query) use ($slug) {
66
            $query->whereSlug($slug);
67
            $query->whereLocale(app()->getLocale());
68
        })->with(['slugs']);
69
    }
70
71
    public function scopeForFallbackLocaleSlug($query, $slug)
72 21
    {
73
        return $query->whereHas('slugs', function ($query) use ($slug) {
74 21
            $query->whereSlug($slug);
75 21
            $query->whereActive(true);
76
            $query->whereLocale(config('translatable.fallback_locale'));
77 21
        })->with(['slugs']);
78
    }
79 21
80
    public function setSlugs($restoring = false)
81 21
    {
82
        foreach ($this->getSlugParams() as $slugParams) {
83
            $this->updateOrNewSlug($slugParams, $restoring);
84 21
        }
85
    }
86
87
    public function updateOrNewSlug($slugParams, $restoring = false)
88 21
    {
89 21
        if (in_array($slugParams['locale'], config('twill.slug_utf8_languages', []))) {
90 21
            $slugParams['slug'] = $this->getUtf8Slug($slugParams['slug']);
91 21
        } else {
92 21
            $slugParams['slug'] = Str::slug($slugParams['slug']);
93
        }
94
95
        //active old slug if already existing or create a new one
96 21
        if ((($oldSlug = $this->getExistingSlug($slugParams)) != null)
97
            && ($restoring ? $slugParams['slug'] === $this->suffixSlugIfExisting($slugParams) : true)) {
98 21
            if (!$oldSlug->active && ($slugParams['active'] ?? false)) {
99
                DB::table($this->getSlugsTable())->where('id', $oldSlug->id)->update(['active' => 1]);
100 21
                $this->disableLocaleSlugs($oldSlug->locale, $oldSlug->id);
101
            }
102 21
        } else {
103 21
104
            $this->addOneSlug($slugParams);
105 21
        }
106
    }
107 21
108
    public function getExistingSlug($slugParams)
109 21
    {
110 21
        $query = DB::table($this->getSlugsTable())->where($this->getForeignKey(), $this->id);
111 21
        unset($slugParams['active']);
112 21
113
        foreach ($slugParams as $key => $value) {
114 21
            //check variations of the slug
115
            if ($key == 'slug') {
116 21
                $query->where(function ($query) use ($value) {
117
                    $query->orWhere('slug', $value);
118
                    $query->orWhere('slug', $value . '-' . $this->getSuffixSlug());
119
                    for ($i = 2; $i <= $this->nb_variation_slug; $i++) {
120 21
                        $query->orWhere('slug', $value . '-' . $i);
121
                    }
122
                });
123 21
            } else {
124
                $query->where($key, $value);
125 21
            }
126 21
        }
127 21
128
        return $query->first();
129
    }
130 21
131
    protected function addOneSlug($slugParams)
132 21
    {
133
        $datas = [];
134 21
        foreach ($slugParams as $key => $value) {
135
            $datas[$key] = $value;
136 21
        }
137 21
138
        $datas['slug'] = $this->suffixSlugIfExisting($slugParams);
139 21
140
        $datas[$this->getForeignKey()] = $this->id;
141 21
142 21
        $id = DB::table($this->getSlugsTable())->insertGetId($datas);
143 21
144 21
        $this->disableLocaleSlugs($slugParams['locale'], $id);
145 21
    }
146
147 21
    public function disableLocaleSlugs($locale, $except_slug_id = 0)
148
    {
149 21
        DB::table($this->getSlugsTable())
150
            ->where($this->getForeignKey(), $this->id)
151 21
            ->where('id', '<>', $except_slug_id)
152 21
            ->where('locale', $locale)
153
            ->update(['active' => 0])
154 21
        ;
155
    }
156 21
157 21
    private function suffixSlugIfExisting($slugParams)
158 21
    {
159 21
        $slugBackup = $slugParams['slug'];
160 21
        $table = $this->getSlugsTable();
161
162
        unset($slugParams['active']);
163 21
164 21
        for ($i = 2; $i <= $this->nb_variation_slug + 1; $i++) {
165
            $qCheck = DB::table($table);
166
            $qCheck->whereNull($this->getDeletedAtColumn());
0 ignored issues
show
Bug introduced by
It seems like getDeletedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

166
            $qCheck->whereNull($this->/** @scrutinizer ignore-call */ getDeletedAtColumn());
Loading history...
167
            foreach ($slugParams as $key => $value) {
168
                $qCheck->where($key, '=', $value);
169
            }
170
171
            if ($qCheck->first() == null) {
172 21
                break;
173
            }
174
175
            if (!empty($slugParams['slug'])) {
176
                $slugParams['slug'] = $slugBackup . (($i > $this->nb_variation_slug) ? "-" . $this->getSuffixSlug() : "-{$i}");
177
            }
178
        }
179
180
        return $slugParams['slug'];
181
    }
182
183
    public function getActiveSlug($locale = null)
184
    {
185
        return $this->slugs->first(function ($slug) use ($locale) {
186
            return ($slug->locale === ($locale ?? app()->getLocale())) && $slug->active;
187
        }) ?? null;
188
    }
189
190
    public function getFallbackActiveSlug()
191
    {
192
        return $this->slugs->first(function ($slug) {
193
            return $slug->locale === config('translatable.fallback_locale') && $slug->active;
194
        }) ?? null;
195
    }
196
197
    public function getSlug($locale = null)
198
    {
199
        if (($slug = $this->getActiveSlug($locale)) != null) {
200
            return $slug->slug;
201
        }
202
203
        if (config('translatable.use_property_fallback', false) && (($slug = $this->getFallbackActiveSlug()) != null)) {
204
            return $slug->slug;
205
        }
206
207 21
        return "";
208
    }
209 21
210
    public function getSlugAttribute()
211
    {
212
        return $this->getSlug();
213
    }
214
215
    public function getSlugParams($locale = null)
216 21
    {
217 21
        if (count(getLocales()) === 1 || !isset($this->translations)) {
218 21
            $slugParams = $this->getSingleSlugParams($locale);
219 21
            if ($slugParams != null && !empty($slugParams)) {
220
                return $slugParams;
221 21
            }
222
        }
223 21
224 21
        $slugParams = [];
225
        foreach ($this->translations as $translation) {
226
            if ($translation->locale == $locale || $locale == null) {
227
                $attributes = $this->slugAttributes;
228
229
                $slugAttribute = array_shift($attributes);
230
231
                $slugDependenciesAttributes = [];
232 21
                foreach ($attributes as $attribute) {
233
                    if (!isset($this->$attribute)) {
234
                        throw new \Exception("You must define the field {$attribute} in your model");
235
                    }
236
237 21
                    $slugDependenciesAttributes[$attribute] = $this->$attribute;
238 21
                }
239 21
240 21
                if (!isset($translation->$slugAttribute) && !isset($this->$slugAttribute)) {
241
                    throw new \Exception("You must define the field {$slugAttribute} in your model");
242 21
                }
243 21
244
                $slugParam = [
245
                    'active' => $translation->active,
246 21
                    'slug' => $translation->$slugAttribute ?? $this->$slugAttribute,
247
                    'locale' => $translation->locale,
248
                ] + $slugDependenciesAttributes;
249
250 21
                if ($locale != null) {
251
                    return $slugParam;
252
                }
253
254
                $slugParams[] = $slugParam;
255
            }
256
        }
257
258
        return $locale == null ? $slugParams : null;
259
    }
260
261
    public function getSingleSlugParams($locale = null)
262
    {
263
        $slugParams = [];
264
        foreach (getLocales() as $appLocale) {
265
            if ($appLocale == $locale || $locale == null) {
266
                $attributes = $this->slugAttributes;
267
                $slugAttribute = array_shift($attributes);
268
                $slugDependenciesAttributes = [];
269
                foreach ($attributes as $attribute) {
270
                    if (!isset($this->$attribute)) {
271
                        throw new \Exception("You must define the field {$attribute} in your model");
272
                    }
273
274
                    $slugDependenciesAttributes[$attribute] = $this->$attribute;
275
                }
276
277
                if (!isset($this->$slugAttribute)) {
278
                    throw new \Exception("You must define the field {$slugAttribute} in your model");
279
                }
280
281
                $slugParam = [
282
                    'active' => 1,
283
                    'slug' => $this->$slugAttribute,
284
                    'locale' => $appLocale,
285
                ] + $slugDependenciesAttributes;
286
287
                if ($locale != null) {
288
                    return $slugParam;
289
                }
290 21
291
                $slugParams[] = $slugParam;
292 21
            }
293
        }
294
295 32
        return $locale == null ? $slugParams : null;
296
    }
297 32
298
    public function getSlugsTable()
299
    {
300 21
        return $this->slugs()->getRelated()->getTable();
301
    }
302 21
303
    public function getForeignKey()
304
    {
305
        return Str::snake(class_basename(get_class($this))) . "_id";
306
    }
307
308
    protected function getSuffixSlug()
309
    {
310
        return $this->id;
311
    }
312
313
    public function getUtf8Slug($str, $options = [])
314
    {
315
        // Make sure string is in UTF-8 and strip invalid UTF-8 characters
316
        $str = mb_convert_encoding((string) $str, 'UTF-8', mb_list_encodings());
317
318
        $defaults = array(
319
            'delimiter' => '-',
320
            'limit' => null,
321
            'lowercase' => true,
322
            'replacements' => array(),
323
            'transliterate' => true,
324
        );
325
326
        // Merge options
327
        $options = array_merge($defaults, $options);
328
329
        $char_map = array(
330
            // Latin
331
            'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C',
332
            'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
333
            'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O',
334
            'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH',
335
            'ß' => 'ss',
336
            'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c',
337
            'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
338
            'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o',
339
            'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th',
340
            'ÿ' => 'y',
341
342
            // Latin symbols
343
            '©' => '(c)',
344
345
            // Greek
346
            'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', 'Θ' => '8',
347
            'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', 'Ο' => 'O', 'Π' => 'P',
348
            'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W',
349
            'Ά' => 'A', 'Έ' => 'E', 'Ί' => 'I', 'Ό' => 'O', 'Ύ' => 'Y', 'Ή' => 'H', 'Ώ' => 'W', 'Ϊ' => 'I',
350
            'Ϋ' => 'Y',
351
            'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8',
352
            'ι' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p',
353
            'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w',
354
            'ά' => 'a', 'έ' => 'e', 'ί' => 'i', 'ό' => 'o', 'ύ' => 'y', 'ή' => 'h', 'ώ' => 'w', 'ς' => 's',
355
            'ϊ' => 'i', 'ΰ' => 'y', 'ϋ' => 'y', 'ΐ' => 'i',
356
357
            // Turkish
358
            'Ş' => 'S', 'İ' => 'I', 'Ç' => 'C', 'Ü' => 'U', 'Ö' => 'O', 'Ğ' => 'G',
359
            'ş' => 's', 'ı' => 'i', 'ç' => 'c', 'ü' => 'u', 'ö' => 'o', 'ğ' => 'g',
360
361
            // Russian
362
            'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh',
363
            'З' => 'Z', 'И' => 'I', 'Й' => 'J', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O',
364
            'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C',
365
            'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sh', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'Yu',
366
            'Я' => 'Ya',
367
            'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh',
368
            'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o',
369
            'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c',
370
            'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sh', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e', 'ю' => 'yu',
371
            'я' => 'ya',
372
373
            // Ukrainian
374
            'Є' => 'Ye', 'І' => 'I', 'Ї' => 'Yi', 'Ґ' => 'G',
375
            'є' => 'ye', 'і' => 'i', 'ї' => 'yi', 'ґ' => 'g',
376
377
            // Kazakh
378
            'Ә' => 'A', 'Ғ' => 'G', 'Қ' => 'Q', 'Ң' => 'N', 'Ө' => 'O', 'Ұ' => 'U',
379
            'ә' => 'a', 'ғ' => 'g', 'қ' => 'q', 'ң' => 'n', 'ө' => 'o', 'ұ' => 'u',
380
381
            // Czech
382
            'Č' => 'C', 'Ď' => 'D', 'Ě' => 'E', 'Ň' => 'N', 'Ř' => 'R', 'Š' => 'S', 'Ť' => 'T', 'Ů' => 'U',
383
            'Ž' => 'Z',
384
            'č' => 'c', 'ď' => 'd', 'ě' => 'e', 'ň' => 'n', 'ř' => 'r', 'š' => 's', 'ť' => 't', 'ů' => 'u',
385
            'ž' => 'z',
386
387
            // Polish
388
            'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'e', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'o', 'Ś' => 'S', 'Ź' => 'Z',
389
            'Ż' => 'Z',
390
            'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z',
391
            'ż' => 'z',
392
393
            // Latvian
394
            'Ā' => 'A', 'Č' => 'C', 'Ē' => 'E', 'Ģ' => 'G', 'Ī' => 'i', 'Ķ' => 'k', 'Ļ' => 'L', 'Ņ' => 'N',
395
            'Š' => 'S', 'Ū' => 'u', 'Ž' => 'Z',
396
            'ā' => 'a', 'č' => 'c', 'ē' => 'e', 'ģ' => 'g', 'ī' => 'i', 'ķ' => 'k', 'ļ' => 'l', 'ņ' => 'n',
397
            'š' => 's', 'ū' => 'u', 'ž' => 'z',
398
399
            // Romanian
400
            'Ă' => 'A', 'Â' => 'A', 'Î' => 'I', 'Ș' => 'S', 'Ț' => 'T',
401
            'ă' => 'a', 'â' => 'a', 'î' => 'i', 'ș' => 's', 'ț' => 't',
402
        );
403
404
        // Make custom replacements
405
        $str = preg_replace(array_keys($options['replacements']), $options['replacements'], $str);
406
407
        // Transliterate characters to ASCII
408
        if ($options['transliterate']) {
409
            $str = str_replace(array_keys($char_map), $char_map, $str);
410
        }
411
412
        // Replace non-alphanumeric characters with our delimiter
413
        $str = preg_replace('/[^\p{L}\p{Nd}]+/u', $options['delimiter'], $str);
414
415
        // Remove duplicate delimiters
416
        $str = preg_replace('/(' . preg_quote($options['delimiter'], '/') . '){2,}/', '$1', $str);
417
418
        // Truncate slug to max. characters
419
        $str = mb_substr($str, 0, ($options['limit'] ? $options['limit'] : mb_strlen($str, 'UTF-8')), 'UTF-8');
420
421
        // Remove delimiter from ends
422
        $str = trim($str, $options['delimiter']);
423
424
        return $options['lowercase'] ? mb_strtolower($str, 'UTF-8') : $str;
425
    }
426
427
    public function urlSlugShorter($string)
428
    {
429
        return strtolower(trim(preg_replace('~[^0-9a-z]+~i', '-', html_entity_decode(preg_replace('~&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', htmlentities($string, ENT_QUOTES, 'UTF-8')), ENT_QUOTES, 'UTF-8')), '-'));
430
    }
431
}
432