Test Setup Failed
Pull Request — master (#557)
by Tom
359:00 queued 356:19
created

Translatable::getAttribute()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 10
cts 10
cp 1
rs 9.552
c 0
b 0
f 0
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
namespace Dimsav\Translatable;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Str;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Builder;
9
use Illuminate\Database\Query\JoinClause;
10
use Illuminate\Database\Eloquent\Collection;
11
use Illuminate\Database\Eloquent\Relations\HasMany;
12
use Illuminate\Database\Eloquent\Relations\Relation;
13
use Illuminate\Database\Query\Builder as QueryBuilder;
14
use Dimsav\Translatable\Exception\LocalesNotDefinedException;
15
16
/**
17
 * @property-read Collection|Model[] $translations
18
 * @property-read string $translationModel
19
 * @property-read string $translationForeignKey
20
 * @property-read string $localeKey
21
 * @property-read bool $useTranslationFallback
22
 *
23
 * @mixin Model
24
 */
25
trait Translatable
26
{
27
    protected static $autoloadTranslations = null;
28
29
    protected $defaultLocale;
30
31 52
    public function translate(?string $locale = null, bool $withFallback = false): ?Model
32
    {
33 52
        return $this->getTranslation($locale, $withFallback);
34
    }
35
36 4
    public function translateOrDefault(?string $locale = null): ?Model
37
    {
38 4
        return $this->getTranslation($locale, true);
39
    }
40
41 4
    public function translateOrNew(?string $locale = null): Model
42
    {
43 4
        return $this->getTranslationOrNew($locale);
44
    }
45
46 248
    public function getTranslation(?string $locale = null, bool $withFallback = null): ?Model
47
    {
48 248
        $configFallbackLocale = $this->getFallbackLocale();
49 248
        $locale = $locale ?: $this->locale();
50 248
        $withFallback = $withFallback === null ? $this->useFallback() : $withFallback;
51 248
        $fallbackLocale = $this->getFallbackLocale($locale);
52
53 248
        if ($translation = $this->getTranslationByLocaleKey($locale)) {
54 140
            return $translation;
55
        }
56
57 168
        if ($withFallback && $fallbackLocale) {
58 28
            if ($translation = $this->getTranslationByLocaleKey($fallbackLocale)) {
59 16
                return $translation;
60
            }
61
62 12
            if ($fallbackLocale !== $configFallbackLocale && $translation = $this->getTranslationByLocaleKey($configFallbackLocale)) {
63 8
                return $translation;
64
            }
65
        }
66
67 164
        return null;
68
    }
69
70 12
    public function hasTranslation(?string $locale = null): bool
71
    {
72 12
        $locale = $locale ?: $this->locale();
73
74 12
        foreach ($this->translations as $translation) {
75 4
            if ($translation->getAttribute($this->getLocaleKey()) == $locale) {
76 4
                return true;
77
            }
78
        }
79
80 12
        return false;
81
    }
82
83 312
    public function getTranslationModelName(): string
84
    {
85 312
        return $this->translationModel ?: $this->getTranslationModelNameDefault();
86
    }
87
88 304
    public function getTranslationModelNameDefault(): string
89
    {
90 304
        $modelName = get_class($this);
91
92 304
        if ($namespace = $this->getTranslationModelNamespace()) {
93 4
            $modelName = $namespace.'\\'.class_basename(get_class($this));
94
        }
95
96 304
        return $modelName.config('translatable.translation_suffix', 'Translation');
97
    }
98
99 304
    public function getTranslationModelNamespace(): ?string
100
    {
101 304
        return config('translatable.translation_model_namespace');
102
    }
103
104 312
    public function getRelationKey(): string
105
    {
106 312
        if ($this->translationForeignKey) {
107 24
            return $this->translationForeignKey;
108 292
        } elseif ($this->primaryKey !== 'id') {
0 ignored issues
show
Bug introduced by
The property primaryKey does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
109
            return $this->primaryKey;
110
        }
111
112 292
        return $this->getForeignKey();
0 ignored issues
show
Bug introduced by
It seems like getForeignKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
113
    }
114
115 292
    public function getLocaleKey(): string
116
    {
117 292
        return $this->localeKey ?: config('translatable.locale_key', 'locale');
118
    }
119
120 292
    public function translations(): HasMany
121
    {
122 292
        return $this->hasMany($this->getTranslationModelName(), $this->getRelationKey());
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?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
123
    }
124
125 12
    private function usePropertyFallback(): bool
126
    {
127 12
        return $this->useFallback() && config('translatable.use_property_fallback', false);
128
    }
129
130 92
    private function getAttributeOrFallback(?string $locale, string $attribute)
131
    {
132 92
        $translation = $this->getTranslation($locale); // ToDo: BC - do not use double fallback
133
134
        if (
135
            (
136 92
                ! $translation instanceof Model
137 92
                || empty($translation->getAttribute($attribute))
138
            )
139 92
            && $this->usePropertyFallback()
140
        ) {
141 8
            $translation = $this->getTranslation($this->getFallbackLocale(), false);
142
        }
143
144 92
        if ($translation instanceof Model) {
145 88
            return $translation->getAttribute($attribute);
146
        }
147
148 8
        return null;
149
    }
150
151 388
    public function getAttribute($key)
152
    {
153 388
        list($attribute, $locale) = $this->getAttributeAndLocale($key);
154
155 388
        if ($this->isTranslationAttribute($attribute)) {
156 72
            if ($this->getTranslation($locale) === null) {
157 12
                return $this->getAttributeValue($attribute);
0 ignored issues
show
Bug introduced by
The method getAttributeValue() does not exist on Dimsav\Translatable\Translatable. Did you maybe mean getAttribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
158
            }
159
160
            // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue
161
            // on it. This way, we can use Eloquent's checking for Mutation, type casting, and
162
            // Date fields.
163 60
            if ($this->hasGetMutator($attribute)) {
0 ignored issues
show
Bug introduced by
It seems like hasGetMutator() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
164 4
                $this->attributes[$attribute] = $this->getAttributeOrFallback($locale, $attribute);
0 ignored issues
show
Bug introduced by
The property attributes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
165
166 4
                return $this->getAttributeValue($attribute);
0 ignored issues
show
Bug introduced by
The method getAttributeValue() does not exist on Dimsav\Translatable\Translatable. Did you maybe mean getAttribute()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
167
            }
168
169 56
            return $this->getAttributeOrFallback($locale, $attribute);
170
        }
171
172 388
        return parent::getAttribute($key);
173
    }
174
175 388
    public function setAttribute($key, $value)
176
    {
177 388
        list($attribute, $locale) = $this->getAttributeAndLocale($key);
178
179 388
        if ($this->isTranslationAttribute($attribute)) {
180 40
            $this->getTranslationOrNew($locale)->$attribute = $value;
181
        } else {
182 388
            return parent::setAttribute($key, $value);
183
        }
184
185 40
        return $this;
186
    }
187
188 388
    public function save(array $options = []): bool
189
    {
190 388
        if ($this->exists && ! $this->isDirty()) {
0 ignored issues
show
Bug introduced by
The property exists does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
It seems like isDirty() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
191
            // If $this->exists and not dirty, parent::save() skips saving and returns
192
            // false. So we have to save the translations
193 24
            if ($this->fireModelEvent('saving') === false) {
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
194
                return false;
195
            }
196
197 24
            if ($saved = $this->saveTranslations()) {
198 24
                $this->fireModelEvent('saved', false);
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
199 24
                $this->fireModelEvent('updated', false);
0 ignored issues
show
Bug introduced by
It seems like fireModelEvent() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
200
            }
201
202 24
            return $saved;
203
        }
204
205
        // We save the translations only if the instance is saved in the database.
206 388
        if (parent::save($options)) {
207 388
            return $this->saveTranslations();
208
        }
209
210 8
        return false;
211
    }
212
213 140
    protected function getTranslationOrNew(?string $locale = null): Model
214
    {
215 140
        $locale = $locale ?: $this->locale();
216
217 140
        if (($translation = $this->getTranslation($locale, false)) === null) {
218 124
            $translation = $this->getNewTranslation($locale);
219
        }
220
221 140
        return $translation;
222
    }
223
224 388
    public function fill(array $attributes)
225
    {
226 388
        foreach ($attributes as $key => $values) {
227 112
            if ($this->isKeyALocale($key)) {
228 48
                $this->getTranslationOrNew($key)->fill($values);
229 40
                unset($attributes[$key]);
230
            } else {
231 100
                list($attribute, $locale) = $this->getAttributeAndLocale($key);
232 100
                if ($this->isTranslationAttribute($attribute) and $this->isKeyALocale($locale)) {
233 48
                    $this->getTranslationOrNew($locale)->fill([$attribute => $values]);
234 48
                    unset($attributes[$key]);
235
                }
236
            }
237
        }
238
239 388
        return parent::fill($attributes);
240
    }
241
242 248
    private function getTranslationByLocaleKey(string $key): ?Model
243
    {
244 248
        foreach ($this->translations as $translation) {
245 196
            if ($translation->getAttribute($this->getLocaleKey()) == $key) {
246 164
                return $translation;
247
            }
248
        }
249
250 168
        return null;
251
    }
252
253 252
    private function getFallbackLocale(?string $locale = null): string
254
    {
255 252
        if ($locale && $this->isLocaleCountryBased($locale)) {
256 28
            if ($fallback = $this->getLanguageFromCountryBasedLocale($locale)) {
257 28
                return $fallback;
258
            }
259
        }
260
261 252
        return config('translatable.fallback_locale');
262
    }
263
264 248
    private function isLocaleCountryBased(?string $locale): bool
265
    {
266 248
        return strpos($locale, $this->getLocaleSeparator()) !== false;
267
    }
268
269 28
    private function getLanguageFromCountryBasedLocale(string $locale): string
270
    {
271 28
        $parts = explode($this->getLocaleSeparator(), $locale);
272
273 28
        return Arr::get($parts, 0);
274
    }
275
276 144
    private function useFallback(): bool
277
    {
278 144
        if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) {
279 12
            return $this->useTranslationFallback;
280
        }
281
282 132
        return config('translatable.use_fallback');
283
    }
284
285 388
    public function isTranslationAttribute(string $key): bool
286
    {
287 388
        return in_array($key, $this->translatedAttributes);
0 ignored issues
show
Bug introduced by
The property translatedAttributes does not seem to exist. Did you mean attributes?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
288
    }
289
290 112
    protected function isKeyALocale(string $key): bool
291
    {
292 112
        $locales = $this->getLocales();
293
294 108
        return in_array($key, $locales);
295
    }
296
297 112
    protected function getLocales(): array
298
    {
299 112
        $localesConfig = (array) config('translatable.locales', []);
300
301 112
        if (empty($localesConfig)) {
302 4
            throw new LocalesNotDefinedException('Please make sure you have run "php artisan config:publish dimsav/laravel-translatable" and that the locales configuration is defined.');
303
        }
304
305 108
        $locales = [];
306 108
        foreach ($localesConfig as $key => $locale) {
307 108
            if (is_array($locale)) {
308 20
                $locales[] = $key;
309 20
                foreach ($locale as $countryLocale) {
310 20
                    $locales[] = $key.$this->getLocaleSeparator().$countryLocale;
311
                }
312
            } else {
313 100
                $locales[] = $locale;
314
            }
315
        }
316
317 108
        return $locales;
318
    }
319
320 248
    protected function getLocaleSeparator(): string
321
    {
322 248
        return config('translatable.locale_separator', '-');
323
    }
324
325 388
    protected function saveTranslations(): bool
326
    {
327 388
        $saved = true;
328
329 388
        if (! $this->relationLoaded('translations')) {
0 ignored issues
show
Bug introduced by
It seems like relationLoaded() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
330 388
            return $saved;
331
        }
332
333 100
        foreach ($this->translations as $translation) {
334 100
            if ($saved && $this->isTranslationDirty($translation)) {
335 100
                if (! empty($connectionName = $this->getConnectionName())) {
0 ignored issues
show
Bug introduced by
It seems like getConnectionName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
336 100
                    $translation->setConnection($connectionName);
337
                }
338
339 100
                $translation->setAttribute($this->getRelationKey(), $this->getKey());
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
340 100
                $saved = $translation->save();
341
            }
342
        }
343
344 96
        return $saved;
345
    }
346
347 4
    public function replicateWithTranslations(array $except = null): Model
348
    {
349 4
        $newInstance = parent::replicate($except);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (replicate() instead of replicateWithTranslations()). Are you sure this is correct? If so, you might want to change this to $this->replicate().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
350
351 4
        unset($newInstance->translations);
352 4
        foreach ($this->translations as $translation) {
353 4
            $newTranslation = $translation->replicate();
354 4
            $newInstance->translations->add($newTranslation);
355
        }
356
357 4
        return $newInstance;
358
    }
359
360 100
    protected function isTranslationDirty(Model $translation): bool
361
    {
362 100
        $dirtyAttributes = $translation->getDirty();
363 100
        unset($dirtyAttributes[$this->getLocaleKey()]);
364
365 100
        return ! empty($dirtyAttributes);
366
    }
367
368 128
    public function getNewTranslation(?string $locale): Model
369
    {
370 128
        $modelName = $this->getTranslationModelName();
371 128
        $translation = new $modelName();
372 128
        $translation->setAttribute($this->getLocaleKey(), $locale);
373 128
        $this->translations->add($translation);
374
375 128
        return $translation;
376
    }
377
378 148
    public function __isset($key): bool
379
    {
380 148
        return $this->isTranslationAttribute($key) || parent::__isset($key);
381
    }
382
383 8
    public function scopeTranslatedIn(Builder $query, ?string $locale = null): Builder
384
    {
385 8
        $locale = $locale ?: $this->locale();
386
387
        return $query->whereHas('translations', function (Builder $q) use ($locale) {
388 8
            $q->where($this->getLocaleKey(), '=', $locale);
389 8
        });
390
    }
391
392 8
    public function scopeNotTranslatedIn(Builder $query, ?string $locale = null): Builder
393
    {
394 8
        $locale = $locale ?: $this->locale();
395
396
        return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) {
397 8
            $q->where($this->getLocaleKey(), '=', $locale);
398 8
        });
399
    }
400
401 4
    public function scopeTranslated(Builder $query): Builder
402
    {
403 4
        return $query->has('translations');
404
    }
405
406 12
    public function scopeListsTranslations(Builder $query, string $translationField): Builder
407
    {
408 12
        $withFallback = $this->useFallback();
409 12
        $translationTable = $this->getTranslationsTable();
410 12
        $localeKey = $this->getLocaleKey();
411
412
        $query
0 ignored issues
show
Bug introduced by
The method select() does not exist on Illuminate\Database\Eloquent\Builder. Did you maybe mean createSelectWithConstraint()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
413 12
            ->select($this->getTable().'.'.$this->getKeyName(), $translationTable.'.'.$translationField)
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
Bug introduced by
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
414 12
            ->leftJoin($translationTable, $translationTable.'.'.$this->getRelationKey(), '=', $this->getTable().'.'.$this->getKeyName())
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
Bug introduced by
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
415 12
            ->where($translationTable.'.'.$localeKey, $this->locale());
416 12
        if ($withFallback) {
417
            $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) {
418 4
                $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale())
419
                  ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use (
420 4
                      $translationTable,
421 4
                      $localeKey
422
                  ) {
423 4
                      $q->select($translationTable.'.'.$this->getRelationKey())
424 4
                        ->from($translationTable)
425 4
                        ->where($translationTable.'.'.$localeKey, $this->locale());
426 4
                  });
427 4
            });
428
        }
429
430 12
        return $query;
431
    }
432
433 12
    public function scopeWithTranslation(Builder $query): Builder
434
    {
435 12
        return $query->with([
436
            'translations' => function (Relation $query) {
437 12
                if ($this->useFallback()) {
438 8
                    $locale = $this->locale();
439 8
                    $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de
440 8
                    $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]);
441
442 8
                    return $query->whereIn($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locales);
0 ignored issues
show
Bug introduced by
The method whereIn() does not exist on Illuminate\Database\Eloquent\Relations\Relation. Did you maybe mean whereInMethod()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
443
                }
444
445 4
                return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale());
446 12
            },
447
        ]);
448
    }
449
450 24
    public function scopeWhereTranslation(Builder $query, string $key, $value, ?string $locale = null, bool $or = false, string $operator = '=')
451
    {
452
        return $query->{$or ? 'orWhereHas' : 'whereHas'}('translations', function (Builder $query) use ($key, $operator, $value, $locale) {
453 24
            $query->where($this->getTranslationsTable().'.'.$key, $operator, $value);
454 24
            if ($locale) {
455 8
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $operator, $locale); // ToDo: BC - the locale should be an equal comparison
456
            }
457 24
        });
458
    }
459
460 4
    public function scopeOrWhereTranslation(Builder $query, string $key, $value, ?string $locale = null)
461
    {
462 4
        return $this->scopeWhereTranslation($query, $key, $value, $locale, true);
463
    }
464
465 12
    public function scopeWhereTranslationLike(Builder $query, string $key, $value, ?string $locale = null): Builder
466
    {
467 12
        return $this->scopeWhereTranslation($query, $key, $value, $locale, false, 'LIKE');
468
    }
469
470 4
    public function scopeOrWhereTranslationLike(Builder $query, string $key, $value, ?string $locale = null): Builder
471
    {
472 4
        return $this->scopeWhereTranslation($query, $key, $value, $locale, true, 'LIKE');
473
    }
474
475 8
    public function scopeOrderByTranslation(Builder $query, string $key, string $sortMethod = 'asc'): Builder
476
    {
477 8
        $translationTable = $this->getTranslationsTable();
478 8
        $localeKey = $this->getLocaleKey();
479 8
        $table = $this->getTable();
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
480 8
        $keyName = $this->getKeyName();
0 ignored issues
show
Bug introduced by
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
481
482
        return $query
483
            ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) {
484
                $join
485 8
                    ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName)
486 8
                    ->where($translationTable.'.'.$localeKey, $this->locale());
487 8
            })
488 8
            ->orderBy($translationTable.'.'.$key, $sortMethod)
489 8
            ->select($table.'.*')
490 8
            ->with('translations');
491
    }
492
493 48
    public function attributesToArray()
494
    {
495 48
        $attributes = parent::attributesToArray();
496
497
        if (
498 48
            (! $this->relationLoaded('translations') && ! $this->toArrayAlwaysLoadsTranslations() && is_null(self::$autoloadTranslations))
0 ignored issues
show
Bug introduced by
It seems like relationLoaded() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
499 48
            || self::$autoloadTranslations === false
500
        ) {
501 16
            return $attributes;
502
        }
503
504 32
        $hiddenAttributes = $this->getHidden();
0 ignored issues
show
Bug introduced by
It seems like getHidden() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
505
506 32
        foreach ($this->translatedAttributes as $field) {
0 ignored issues
show
Bug introduced by
The property translatedAttributes does not seem to exist. Did you mean attributes?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
507 32
            if (in_array($field, $hiddenAttributes)) {
508 4
                continue;
509
            }
510
511 32
            $attributes[$field] = $this->getAttributeOrFallback(null, $field);
512
        }
513
514 32
        return $attributes;
515
    }
516
517 4
    public function getTranslationsArray(): array
518
    {
519 4
        $translations = [];
520
521 4
        foreach ($this->translations as $translation) {
522 4
            foreach ($this->translatedAttributes as $attr) {
0 ignored issues
show
Bug introduced by
The property translatedAttributes does not seem to exist. Did you mean attributes?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
523 4
                $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr};
524
            }
525
        }
526
527 4
        return $translations;
528
    }
529
530 56
    private function getTranslationsTable(): string
531
    {
532 56
        return app()->make($this->getTranslationModelName())->getTable();
533
    }
534
535 388
    protected function locale(): string
536
    {
537 388
        if ($this->defaultLocale) {
538 4
            return $this->defaultLocale;
539
        }
540
541 388
        return config('translatable.locale')
542 388
            ?: app()->make('translator')->getLocale();
543
    }
544
545 4
    public function setDefaultLocale(?string $locale)
546
    {
547 4
        $this->defaultLocale = $locale;
548
549 4
        return $this;
550
    }
551
552
    public function getDefaultLocale(): ?string
553
    {
554
        return $this->defaultLocale;
555
    }
556
557 12
    public function deleteTranslations($locales = null)
558
    {
559 12
        if ($locales === null) {
560 4
            $translations = $this->translations()->get();
561
        } else {
562 8
            $translations = $this->translations()->whereIn($this->getLocaleKey(), (array) $locales)->get();
0 ignored issues
show
Bug introduced by
The method whereIn() does not exist on Illuminate\Database\Eloquent\Relations\HasMany. Did you maybe mean whereInMethod()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
563
        }
564
565 12
        foreach ($translations as $translation) {
566 8
            $translation->delete();
567
        }
568
569
        // we need to manually "reload" the collection built from the relationship
570
        // otherwise $this->translations()->get() would NOT be the same as $this->translations
571 12
        $this->load('translations');
0 ignored issues
show
Bug introduced by
It seems like load() must be provided by classes using this trait. How about adding it as abstract method to this trait?

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

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). 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.

Loading history...
572 12
    }
573
574 388
    private function getAttributeAndLocale(string $key): array
575
    {
576 388
        if (Str::contains($key, ':')) {
577 44
            return explode(':', $key);
578
        }
579
580 388
        return [$key, $this->locale()];
581
    }
582
583 32
    private function toArrayAlwaysLoadsTranslations(): bool
584
    {
585 32
        return config('translatable.to_array_always_loads_translations', true);
586
    }
587
588
    public static function enableAutoloadTranslations(): void
589
    {
590
        self::$autoloadTranslations = true;
591
    }
592
593 4
    public static function defaultAutoloadTranslations(): void
594
    {
595 4
        self::$autoloadTranslations = null;
596 4
    }
597
598 4
    public static function disableAutoloadTranslations(): void
599
    {
600 4
        self::$autoloadTranslations = false;
601 4
    }
602
}
603