These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Dimsav\Translatable; |
||
4 | |||
5 | use Illuminate\Database\Eloquent\Model; |
||
6 | use Illuminate\Database\Eloquent\Builder; |
||
7 | use Illuminate\Database\Query\JoinClause; |
||
8 | use Illuminate\Database\Eloquent\Relations\Relation; |
||
9 | use Illuminate\Database\Query\Builder as QueryBuilder; |
||
10 | |||
11 | trait Translatable |
||
12 | { |
||
13 | protected static $autoloadTranslations = null; |
||
14 | |||
15 | protected $defaultLocale; |
||
16 | |||
17 | 480 | public static function bootTranslatable(): void |
|
18 | { |
||
19 | static::saved(function (Model $model) { |
||
20 | /* @var Translatable $model */ |
||
21 | 480 | return $model->saveTranslations(); |
|
22 | 480 | }); |
|
23 | 480 | } |
|
24 | |||
25 | /** |
||
26 | * Alias for getTranslation(). |
||
27 | * |
||
28 | * @param string|null $locale |
||
29 | * @param bool $withFallback |
||
30 | * |
||
31 | * @return \Illuminate\Database\Eloquent\Model|null |
||
32 | */ |
||
33 | 52 | public function translate($locale = null, $withFallback = false) |
|
34 | { |
||
35 | 52 | return $this->getTranslation($locale, $withFallback); |
|
36 | } |
||
37 | |||
38 | /** |
||
39 | * Alias for getTranslation(). |
||
40 | * |
||
41 | * @param string $locale |
||
42 | * |
||
43 | * @return \Illuminate\Database\Eloquent\Model|null |
||
44 | */ |
||
45 | 4 | public function translateOrDefault($locale = null) |
|
46 | { |
||
47 | 4 | return $this->getTranslation($locale, true); |
|
48 | } |
||
49 | |||
50 | /** |
||
51 | * Alias for getTranslationOrNew(). |
||
52 | * |
||
53 | * @param string $locale |
||
54 | * |
||
55 | * @return \Illuminate\Database\Eloquent\Model|null |
||
56 | */ |
||
57 | 4 | public function translateOrNew($locale = null) |
|
58 | { |
||
59 | 4 | return $this->getTranslationOrNew($locale); |
|
60 | } |
||
61 | |||
62 | /** |
||
63 | * @param string|null $locale |
||
64 | * @param bool $withFallback |
||
65 | * |
||
66 | * @return \Illuminate\Database\Eloquent\Model|null |
||
67 | */ |
||
68 | 252 | public function getTranslation($locale = null, $withFallback = null) |
|
69 | { |
||
70 | 252 | $configFallbackLocale = $this->getFallbackLocale(); |
|
71 | 252 | $locale = $locale ?: $this->locale(); |
|
72 | 252 | $withFallback = $withFallback === null ? $this->useFallback() : $withFallback; |
|
73 | 252 | $fallbackLocale = $this->getFallbackLocale($locale); |
|
74 | |||
75 | 252 | if ($translation = $this->getTranslationByLocaleKey($locale)) { |
|
76 | 144 | return $translation; |
|
77 | } |
||
78 | 172 | if ($withFallback && $fallbackLocale) { |
|
79 | 28 | if ($translation = $this->getTranslationByLocaleKey($fallbackLocale)) { |
|
80 | 16 | return $translation; |
|
81 | } |
||
82 | 12 | if ($fallbackLocale !== $configFallbackLocale && $translation = $this->getTranslationByLocaleKey($configFallbackLocale)) { |
|
83 | 8 | return $translation; |
|
84 | } |
||
85 | } |
||
86 | |||
87 | 168 | return null; |
|
88 | } |
||
89 | |||
90 | /** |
||
91 | * @param string|null $locale |
||
92 | * |
||
93 | * @return bool |
||
94 | */ |
||
95 | 12 | public function hasTranslation($locale = null) |
|
96 | { |
||
97 | 12 | $locale = $locale ?: $this->locale(); |
|
98 | |||
99 | 12 | foreach ($this->translations as $translation) { |
|
100 | 4 | if ($translation->getAttribute($this->getLocaleKey()) == $locale) { |
|
101 | 4 | return true; |
|
102 | } |
||
103 | } |
||
104 | |||
105 | 12 | return false; |
|
106 | } |
||
107 | |||
108 | /** |
||
109 | * @return string |
||
110 | */ |
||
111 | 316 | public function getTranslationModelName() |
|
112 | { |
||
113 | 316 | return $this->translationModel ?: $this->getTranslationModelNameDefault(); |
|
114 | } |
||
115 | |||
116 | /** |
||
117 | * @return string |
||
118 | */ |
||
119 | 304 | public function getTranslationModelNameDefault() |
|
120 | { |
||
121 | 304 | $modelName = get_class($this); |
|
122 | |||
123 | 304 | if ($namespace = $this->getTranslationModelNamespace()) { |
|
124 | 4 | $modelName = $namespace.'\\'.class_basename(get_class($this)); |
|
125 | } |
||
126 | |||
127 | 304 | return $modelName.config('translatable.translation_suffix', 'Translation'); |
|
128 | } |
||
129 | |||
130 | /** |
||
131 | * @return string|null |
||
132 | */ |
||
133 | 304 | public function getTranslationModelNamespace() |
|
134 | { |
||
135 | 304 | return config('translatable.translation_model_namespace'); |
|
136 | } |
||
137 | |||
138 | /** |
||
139 | * @return string |
||
140 | */ |
||
141 | 316 | public function getRelationKey() |
|
142 | { |
||
143 | 316 | if ($this->translationForeignKey) { |
|
144 | 28 | return $this->translationForeignKey; |
|
145 | } |
||
146 | |||
147 | 292 | return $this->getForeignKey(); |
|
0 ignored issues
–
show
|
|||
148 | } |
||
149 | |||
150 | /** |
||
151 | * @return string |
||
152 | */ |
||
153 | 296 | public function getLocaleKey() |
|
154 | { |
||
155 | 296 | return $this->localeKey ?: config('translatable.locale_key', 'locale'); |
|
156 | } |
||
157 | |||
158 | /** |
||
159 | * @return \Illuminate\Database\Eloquent\Relations\HasMany |
||
160 | */ |
||
161 | 296 | public function translations() |
|
162 | { |
||
163 | 296 | return $this->hasMany($this->getTranslationModelName(), $this->getRelationKey()); |
|
164 | } |
||
165 | |||
166 | /** |
||
167 | * @return bool |
||
168 | */ |
||
169 | 16 | private function usePropertyFallback() |
|
170 | { |
||
171 | 16 | return $this->useFallback() && config('translatable.use_property_fallback', false); |
|
172 | } |
||
173 | |||
174 | /** |
||
175 | * Returns the attribute value from fallback translation if value of attribute |
||
176 | * is empty and the property fallback is enabled in the configuration. |
||
177 | * in model. |
||
178 | * @param $locale |
||
179 | * @param $attribute |
||
180 | * @return mixed |
||
181 | */ |
||
182 | 96 | private function getAttributeOrFallback($locale, $attribute) |
|
183 | { |
||
184 | 96 | $translation = $this->getTranslation($locale); |
|
185 | |||
186 | if ( |
||
187 | ( |
||
188 | 96 | ! $translation instanceof Model || |
|
189 | 96 | $this->isEmptyTranslatableAttribute($attribute, $translation->$attribute) |
|
190 | ) && |
||
191 | 96 | $this->usePropertyFallback() |
|
192 | ) { |
||
193 | 12 | $translation = $this->getTranslation($this->getFallbackLocale(), false); |
|
194 | } |
||
195 | |||
196 | 96 | if ($translation instanceof Model) { |
|
197 | 92 | return $translation->$attribute; |
|
198 | } |
||
199 | |||
200 | 8 | return null; |
|
201 | } |
||
202 | |||
203 | 88 | protected function isEmptyTranslatableAttribute(string $key, $value): bool |
|
204 | { |
||
205 | 88 | return empty($value); |
|
206 | } |
||
207 | |||
208 | /** |
||
209 | * @param string $key |
||
210 | * |
||
211 | * @return mixed |
||
212 | */ |
||
213 | 480 | public function getAttribute($key) |
|
214 | { |
||
215 | 480 | [$attribute, $locale] = $this->getAttributeAndLocale($key); |
|
216 | |||
217 | 480 | if ($this->isTranslationAttribute($attribute)) { |
|
218 | 76 | if ($this->getTranslation($locale) === null) { |
|
219 | 12 | return $this->getAttributeValue($attribute); |
|
220 | } |
||
221 | |||
222 | // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue |
||
223 | // on it. This way, we can use Eloquent's checking for Mutation, type casting, and |
||
224 | // Date fields. |
||
225 | 64 | if ($this->hasGetMutator($attribute)) { |
|
226 | 4 | $this->attributes[$attribute] = $this->getAttributeOrFallback($locale, $attribute); |
|
227 | |||
228 | 4 | return $this->getAttributeValue($attribute); |
|
229 | } |
||
230 | |||
231 | 60 | return $this->getAttributeOrFallback($locale, $attribute); |
|
232 | } |
||
233 | |||
234 | 480 | return parent::getAttribute($key); |
|
235 | } |
||
236 | |||
237 | /** |
||
238 | * @param string $key |
||
239 | * @param mixed $value |
||
240 | * |
||
241 | * @return $this |
||
242 | */ |
||
243 | 480 | public function setAttribute($key, $value) |
|
244 | { |
||
245 | 480 | [$attribute, $locale] = $this->getAttributeAndLocale($key); |
|
246 | |||
247 | 480 | if ($this->isTranslationAttribute($attribute)) { |
|
248 | 40 | $this->getTranslationOrNew($locale)->$attribute = $value; |
|
249 | } else { |
||
250 | 480 | return parent::setAttribute($key, $value); |
|
251 | } |
||
252 | |||
253 | 40 | return $this; |
|
254 | } |
||
255 | |||
256 | /** |
||
257 | * @param string $locale |
||
258 | * |
||
259 | * @return \Illuminate\Database\Eloquent\Model |
||
260 | */ |
||
261 | 144 | protected function getTranslationOrNew($locale = null) |
|
262 | { |
||
263 | 144 | $locale = $locale ?: $this->locale(); |
|
264 | |||
265 | 144 | if (($translation = $this->getTranslation($locale, false)) === null) { |
|
266 | 128 | $translation = $this->getNewTranslation($locale); |
|
267 | } |
||
268 | |||
269 | 144 | return $translation; |
|
270 | } |
||
271 | |||
272 | /** |
||
273 | * @param array $attributes |
||
274 | * |
||
275 | * @throws \Illuminate\Database\Eloquent\MassAssignmentException |
||
276 | * @return $this |
||
277 | */ |
||
278 | 480 | public function fill(array $attributes) |
|
279 | { |
||
280 | 480 | foreach ($attributes as $key => $values) { |
|
281 | 112 | if ($this->isKeyALocale($key)) { |
|
282 | 52 | $this->getTranslationOrNew($key)->fill($values); |
|
283 | 44 | unset($attributes[$key]); |
|
284 | } else { |
||
285 | 104 | [$attribute, $locale] = $this->getAttributeAndLocale($key); |
|
286 | 104 | if ($this->isTranslationAttribute($attribute) and $this->isKeyALocale($locale)) { |
|
287 | 48 | $this->getTranslationOrNew($locale)->fill([$attribute => $values]); |
|
288 | 48 | unset($attributes[$key]); |
|
289 | } |
||
290 | } |
||
291 | } |
||
292 | |||
293 | 480 | return parent::fill($attributes); |
|
294 | } |
||
295 | |||
296 | /** |
||
297 | * @param string $key |
||
298 | */ |
||
299 | 252 | private function getTranslationByLocaleKey($key) |
|
300 | { |
||
301 | 252 | foreach ($this->translations as $translation) { |
|
302 | 200 | if ($translation->getAttribute($this->getLocaleKey()) == $key) { |
|
303 | 168 | return $translation; |
|
304 | } |
||
305 | } |
||
306 | |||
307 | 172 | return null; |
|
308 | } |
||
309 | |||
310 | /** |
||
311 | * @param null $locale |
||
312 | * |
||
313 | * @return string |
||
314 | */ |
||
315 | 256 | private function getFallbackLocale($locale = null) |
|
316 | { |
||
317 | 256 | if ($locale && $this->isLocaleCountryBased($locale)) { |
|
318 | 28 | if ($fallback = $this->getLanguageFromCountryBasedLocale($locale)) { |
|
319 | 28 | return $fallback; |
|
320 | } |
||
321 | } |
||
322 | |||
323 | 256 | return config('translatable.fallback_locale'); |
|
324 | } |
||
325 | |||
326 | 252 | private function isLocaleCountryBased(string $locale): bool |
|
327 | { |
||
328 | 252 | return $this->getLocalesHelper()->isLocaleCountryBased($locale); |
|
329 | } |
||
330 | |||
331 | 28 | private function getLanguageFromCountryBasedLocale(string $locale): string |
|
332 | { |
||
333 | 28 | return $this->getLocalesHelper()->getLanguageFromCountryBasedLocale($locale); |
|
334 | } |
||
335 | |||
336 | /** |
||
337 | * @return bool|null |
||
338 | */ |
||
339 | 148 | private function useFallback() |
|
340 | { |
||
341 | 148 | if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) { |
|
342 | 12 | return $this->useTranslationFallback; |
|
343 | } |
||
344 | |||
345 | 136 | return config('translatable.use_fallback'); |
|
346 | } |
||
347 | |||
348 | /** |
||
349 | * @param string $key |
||
350 | * |
||
351 | * @return bool |
||
352 | */ |
||
353 | 480 | public function isTranslationAttribute($key) |
|
354 | { |
||
355 | 480 | return in_array($key, $this->translatedAttributes); |
|
356 | } |
||
357 | |||
358 | 112 | protected function isKeyALocale(string $key): bool |
|
359 | { |
||
360 | 112 | return $this->getLocalesHelper()->has($key); |
|
361 | } |
||
362 | |||
363 | protected function getLocales(): array |
||
364 | { |
||
365 | return $this->getLocalesHelper()->all(); |
||
366 | } |
||
367 | |||
368 | protected function getLocaleSeparator(): string |
||
369 | { |
||
370 | return $this->getLocalesHelper()->getLocaleSeparator(); |
||
371 | } |
||
372 | |||
373 | /** |
||
374 | * @return bool |
||
375 | */ |
||
376 | 480 | protected function saveTranslations() |
|
377 | { |
||
378 | 480 | $saved = true; |
|
379 | |||
380 | 480 | if (! $this->relationLoaded('translations')) { |
|
381 | 480 | return $saved; |
|
382 | } |
||
383 | |||
384 | 104 | foreach ($this->translations as $translation) { |
|
385 | 104 | if ($saved && $this->isTranslationDirty($translation)) { |
|
386 | 104 | if (! empty($connectionName = $this->getConnectionName())) { |
|
387 | 104 | $translation->setConnection($connectionName); |
|
388 | } |
||
389 | |||
390 | 104 | $translation->setAttribute($this->getRelationKey(), $this->getKey()); |
|
391 | 104 | $saved = $translation->save(); |
|
392 | } |
||
393 | } |
||
394 | |||
395 | 100 | return $saved; |
|
396 | } |
||
397 | |||
398 | /** |
||
399 | * @param array |
||
400 | * |
||
401 | * @return \Illuminate\Database\Eloquent\Model |
||
402 | */ |
||
403 | 4 | public function replicateWithTranslations(array $except = null) |
|
404 | { |
||
405 | 4 | $newInstance = parent::replicate($except); |
|
406 | |||
407 | 4 | unset($newInstance->translations); |
|
408 | 4 | foreach ($this->translations as $translation) { |
|
409 | 4 | $newTranslation = $translation->replicate(); |
|
410 | 4 | $newInstance->translations->add($newTranslation); |
|
411 | } |
||
412 | |||
413 | 4 | return $newInstance; |
|
414 | } |
||
415 | |||
416 | /** |
||
417 | * @param \Illuminate\Database\Eloquent\Model $translation |
||
418 | * |
||
419 | * @return bool |
||
420 | */ |
||
421 | 104 | protected function isTranslationDirty(Model $translation) |
|
422 | { |
||
423 | 104 | $dirtyAttributes = $translation->getDirty(); |
|
424 | 104 | unset($dirtyAttributes[$this->getLocaleKey()]); |
|
425 | |||
426 | 104 | return count($dirtyAttributes) > 0; |
|
427 | } |
||
428 | |||
429 | /** |
||
430 | * @param string $locale |
||
431 | * |
||
432 | * @return \Illuminate\Database\Eloquent\Model |
||
433 | */ |
||
434 | 132 | public function getNewTranslation($locale) |
|
435 | { |
||
436 | 132 | $modelName = $this->getTranslationModelName(); |
|
437 | 132 | $translation = new $modelName(); |
|
438 | 132 | $translation->setAttribute($this->getLocaleKey(), $locale); |
|
439 | 132 | $this->translations->add($translation); |
|
440 | |||
441 | 132 | return $translation; |
|
442 | } |
||
443 | |||
444 | /** |
||
445 | * @param $key |
||
446 | * |
||
447 | * @return bool |
||
448 | */ |
||
449 | 152 | public function __isset($key) |
|
450 | { |
||
451 | 152 | return $this->isTranslationAttribute($key) || parent::__isset($key); |
|
452 | } |
||
453 | |||
454 | /** |
||
455 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
456 | * @param string $locale |
||
457 | * |
||
458 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
459 | */ |
||
460 | 8 | public function scopeTranslatedIn(Builder $query, $locale = null) |
|
461 | { |
||
462 | 8 | $locale = $locale ?: $this->locale(); |
|
463 | |||
464 | return $query->whereHas('translations', function (Builder $q) use ($locale) { |
||
465 | 8 | $q->where($this->getLocaleKey(), '=', $locale); |
|
466 | 8 | }); |
|
467 | } |
||
468 | |||
469 | /** |
||
470 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
471 | * @param string $locale |
||
472 | * |
||
473 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
474 | */ |
||
475 | 8 | public function scopeNotTranslatedIn(Builder $query, $locale = null) |
|
476 | { |
||
477 | 8 | $locale = $locale ?: $this->locale(); |
|
478 | |||
479 | return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) { |
||
480 | 8 | $q->where($this->getLocaleKey(), '=', $locale); |
|
481 | 8 | }); |
|
482 | } |
||
483 | |||
484 | /** |
||
485 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
486 | * |
||
487 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
488 | */ |
||
489 | 4 | public function scopeTranslated(Builder $query) |
|
490 | { |
||
491 | 4 | return $query->has('translations'); |
|
492 | } |
||
493 | |||
494 | /** |
||
495 | * Adds scope to get a list of translated attributes, using the current locale. |
||
496 | * Example usage: Country::listsTranslations('name')->get()->toArray() |
||
497 | * Will return an array with items: |
||
498 | * [ |
||
499 | * 'id' => '1', // The id of country |
||
500 | * 'name' => 'Griechenland' // The translated name |
||
501 | * ]. |
||
502 | * |
||
503 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
504 | * @param string $translationField |
||
505 | */ |
||
506 | 12 | public function scopeListsTranslations(Builder $query, $translationField) |
|
507 | { |
||
508 | 12 | $withFallback = $this->useFallback(); |
|
509 | 12 | $translationTable = $this->getTranslationsTable(); |
|
510 | 12 | $localeKey = $this->getLocaleKey(); |
|
511 | |||
512 | $query |
||
513 | 12 | ->select($this->getTable().'.'.$this->getKeyName(), $translationTable.'.'.$translationField) |
|
514 | 12 | ->leftJoin($translationTable, $translationTable.'.'.$this->getRelationKey(), '=', $this->getTable().'.'.$this->getKeyName()) |
|
515 | 12 | ->where($translationTable.'.'.$localeKey, $this->locale()); |
|
516 | 12 | if ($withFallback) { |
|
517 | $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) { |
||
518 | 4 | $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale()) |
|
519 | ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use ( |
||
520 | 4 | $translationTable, |
|
521 | 4 | $localeKey |
|
522 | ) { |
||
523 | 4 | $q->select($translationTable.'.'.$this->getRelationKey()) |
|
524 | 4 | ->from($translationTable) |
|
525 | 4 | ->where($translationTable.'.'.$localeKey, $this->locale()); |
|
526 | 4 | }); |
|
527 | 4 | }); |
|
528 | } |
||
529 | 12 | } |
|
530 | |||
531 | /** |
||
532 | * This scope eager loads the translations for the default and the fallback locale only. |
||
533 | * We can use this as a shortcut to improve performance in our application. |
||
534 | * |
||
535 | * @param Builder $query |
||
536 | */ |
||
537 | 12 | public function scopeWithTranslation(Builder $query) |
|
538 | { |
||
539 | 12 | $query->with([ |
|
540 | 'translations' => function (Relation $query) { |
||
541 | 12 | if ($this->useFallback()) { |
|
542 | 8 | $locale = $this->locale(); |
|
543 | 8 | $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de |
|
544 | 8 | $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]); |
|
545 | |||
546 | 8 | return $query->whereIn($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locales); |
|
547 | } |
||
548 | |||
549 | 4 | return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale()); |
|
550 | 12 | }, |
|
551 | ]); |
||
552 | 12 | } |
|
553 | |||
554 | /** |
||
555 | * This scope filters results by checking the translation fields. |
||
556 | * |
||
557 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
558 | * @param string $key |
||
559 | * @param string $value |
||
560 | * @param string $locale |
||
561 | * |
||
562 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
563 | */ |
||
564 | 12 | public function scopeWhereTranslation(Builder $query, $key, $value, $locale = null) |
|
565 | { |
||
566 | return $query->whereHas('translations', function (Builder $query) use ($key, $value, $locale) { |
||
567 | 12 | $query->where($this->getTranslationsTable().'.'.$key, $value); |
|
568 | 12 | if ($locale) { |
|
569 | 4 | $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale); |
|
570 | } |
||
571 | 12 | }); |
|
572 | } |
||
573 | |||
574 | /** |
||
575 | * This scope filters results by checking the translation fields. |
||
576 | * |
||
577 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
578 | * @param string $key |
||
579 | * @param string $value |
||
580 | * @param string $locale |
||
581 | * |
||
582 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
583 | */ |
||
584 | 4 | public function scopeOrWhereTranslation(Builder $query, $key, $value, $locale = null) |
|
585 | { |
||
586 | return $query->orWhereHas('translations', function (Builder $query) use ($key, $value, $locale) { |
||
587 | 4 | $query->where($this->getTranslationsTable().'.'.$key, $value); |
|
588 | 4 | if ($locale) { |
|
589 | $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale); |
||
590 | } |
||
591 | 4 | }); |
|
592 | } |
||
593 | |||
594 | /** |
||
595 | * This scope filters results by checking the translation fields. |
||
596 | * |
||
597 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
598 | * @param string $key |
||
599 | * @param string $value |
||
600 | * @param string $locale |
||
601 | * |
||
602 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
603 | */ |
||
604 | 12 | public function scopeWhereTranslationLike(Builder $query, $key, $value, $locale = null) |
|
605 | { |
||
606 | return $query->whereHas('translations', function (Builder $query) use ($key, $value, $locale) { |
||
607 | 12 | $query->where($this->getTranslationsTable().'.'.$key, 'LIKE', $value); |
|
608 | 12 | if ($locale) { |
|
609 | 4 | $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale); |
|
610 | } |
||
611 | 12 | }); |
|
612 | } |
||
613 | |||
614 | /** |
||
615 | * This scope filters results by checking the translation fields. |
||
616 | * |
||
617 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
618 | * @param string $key |
||
619 | * @param string $value |
||
620 | * @param string $locale |
||
621 | * |
||
622 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
623 | */ |
||
624 | 4 | public function scopeOrWhereTranslationLike(Builder $query, $key, $value, $locale = null) |
|
625 | { |
||
626 | return $query->orWhereHas('translations', function (Builder $query) use ($key, $value, $locale) { |
||
627 | 4 | $query->where($this->getTranslationsTable().'.'.$key, 'LIKE', $value); |
|
628 | 4 | if ($locale) { |
|
629 | $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale); |
||
630 | } |
||
631 | 4 | }); |
|
632 | } |
||
633 | |||
634 | /** |
||
635 | * This scope sorts results by the given translation field. |
||
636 | * |
||
637 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
638 | * @param string $key |
||
639 | * @param string $sortmethod |
||
640 | * |
||
641 | * @return \Illuminate\Database\Eloquent\Builder|static |
||
642 | */ |
||
643 | 8 | public function scopeOrderByTranslation(Builder $query, $key, $sortmethod = 'asc') |
|
644 | { |
||
645 | 8 | $translationTable = $this->getTranslationsTable(); |
|
646 | 8 | $localeKey = $this->getLocaleKey(); |
|
647 | 8 | $table = $this->getTable(); |
|
648 | 8 | $keyName = $this->getKeyName(); |
|
649 | |||
650 | return $query |
||
651 | ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) { |
||
652 | $join |
||
653 | 8 | ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName) |
|
654 | 8 | ->where($translationTable.'.'.$localeKey, $this->locale()); |
|
655 | 8 | }) |
|
656 | 8 | ->orderBy($translationTable.'.'.$key, $sortmethod) |
|
657 | 8 | ->select($table.'.*') |
|
658 | 8 | ->with('translations'); |
|
659 | } |
||
660 | |||
661 | /** |
||
662 | * @return array |
||
663 | */ |
||
664 | 48 | public function attributesToArray() |
|
665 | { |
||
666 | 48 | $attributes = parent::attributesToArray(); |
|
667 | |||
668 | if ( |
||
669 | 48 | (! $this->relationLoaded('translations') && ! $this->toArrayAlwaysLoadsTranslations() && is_null(self::$autoloadTranslations)) |
|
670 | 48 | || self::$autoloadTranslations === false |
|
671 | ) { |
||
672 | 16 | return $attributes; |
|
673 | } |
||
674 | |||
675 | 32 | $hiddenAttributes = $this->getHidden(); |
|
676 | |||
677 | 32 | foreach ($this->translatedAttributes as $field) { |
|
678 | 32 | if (in_array($field, $hiddenAttributes)) { |
|
679 | 4 | continue; |
|
680 | } |
||
681 | |||
682 | 32 | $attributes[$field] = $this->getAttributeOrFallback(null, $field); |
|
683 | } |
||
684 | |||
685 | 32 | return $attributes; |
|
686 | } |
||
687 | |||
688 | /** |
||
689 | * @return array |
||
690 | */ |
||
691 | 4 | public function getTranslationsArray() |
|
692 | { |
||
693 | 4 | $translations = []; |
|
694 | |||
695 | 4 | foreach ($this->translations as $translation) { |
|
696 | 4 | foreach ($this->translatedAttributes as $attr) { |
|
697 | 4 | $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr}; |
|
698 | } |
||
699 | } |
||
700 | |||
701 | 4 | return $translations; |
|
702 | } |
||
703 | |||
704 | /** |
||
705 | * @return string |
||
706 | */ |
||
707 | 56 | private function getTranslationsTable() |
|
708 | { |
||
709 | 56 | return app()->make($this->getTranslationModelName())->getTable(); |
|
710 | } |
||
711 | |||
712 | 480 | protected function locale(): string |
|
713 | { |
||
714 | 480 | if ($this->defaultLocale) { |
|
715 | 4 | return $this->defaultLocale; |
|
716 | } |
||
717 | |||
718 | 480 | return $this->getLocalesHelper()->current(); |
|
719 | } |
||
720 | |||
721 | /** |
||
722 | * Set the default locale on the model. |
||
723 | * |
||
724 | * @param $locale |
||
725 | * |
||
726 | * @return $this |
||
727 | */ |
||
728 | 4 | public function setDefaultLocale($locale) |
|
729 | { |
||
730 | 4 | $this->defaultLocale = $locale; |
|
731 | |||
732 | 4 | return $this; |
|
733 | } |
||
734 | |||
735 | /** |
||
736 | * Get the default locale on the model. |
||
737 | * |
||
738 | * @return mixed |
||
739 | */ |
||
740 | public function getDefaultLocale() |
||
741 | { |
||
742 | return $this->defaultLocale; |
||
743 | } |
||
744 | |||
745 | /** |
||
746 | * Deletes all translations for this model. |
||
747 | * |
||
748 | * @param string|array|null $locales The locales to be deleted (array or single string) |
||
749 | * (e.g., ["en", "de"] would remove these translations). |
||
750 | */ |
||
751 | 12 | public function deleteTranslations($locales = null) |
|
752 | { |
||
753 | 12 | if ($locales === null) { |
|
754 | 4 | $translations = $this->translations()->get(); |
|
755 | } else { |
||
756 | 8 | $locales = (array) $locales; |
|
757 | 8 | $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get(); |
|
758 | } |
||
759 | 12 | foreach ($translations as $translation) { |
|
760 | 8 | $translation->delete(); |
|
761 | } |
||
762 | |||
763 | // we need to manually "reload" the collection built from the relationship |
||
764 | // otherwise $this->translations()->get() would NOT be the same as $this->translations |
||
765 | 12 | $this->load('translations'); |
|
766 | 12 | } |
|
767 | |||
768 | /** |
||
769 | * @param $key |
||
770 | * |
||
771 | * @return array |
||
772 | */ |
||
773 | 480 | private function getAttributeAndLocale($key) |
|
774 | { |
||
775 | 480 | if (str_contains($key, ':')) { |
|
776 | 44 | return explode(':', $key); |
|
777 | } |
||
778 | |||
779 | 480 | return [$key, $this->locale()]; |
|
780 | } |
||
781 | |||
782 | /** |
||
783 | * @return bool |
||
784 | */ |
||
785 | 32 | private function toArrayAlwaysLoadsTranslations() |
|
786 | { |
||
787 | 32 | return config('translatable.to_array_always_loads_translations', true); |
|
788 | } |
||
789 | |||
790 | public static function enableAutoloadTranslations() |
||
791 | { |
||
792 | self::$autoloadTranslations = true; |
||
793 | } |
||
794 | |||
795 | 4 | public static function defaultAutoloadTranslations() |
|
796 | { |
||
797 | 4 | self::$autoloadTranslations = null; |
|
798 | 4 | } |
|
799 | |||
800 | 4 | public static function disableAutoloadTranslations() |
|
801 | { |
||
802 | 4 | self::$autoloadTranslations = false; |
|
803 | 4 | } |
|
804 | |||
805 | 480 | protected function getLocalesHelper(): Locales |
|
806 | { |
||
807 | 480 | return app(Locales::class); |
|
808 | } |
||
809 | } |
||
810 |
This check looks for methods that are used by a trait but not required by it.
To illustrate, let’s look at the following code example
The trait
Idable
provides a methodequalsId
that in turn relies on the methodgetId()
. If this method does not exist on a class mixing in this trait, the method will fail.Adding the
getId()
as an abstract method to the trait will make sure it is available.