Test Setup Failed
Push — ft-locales-helper ( 6f0fc5...b1a615 )
by Tom
63:37
created

Translatable::deleteTranslations()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 5
cts 5
cp 1
rs 9.7333
c 0
b 0
f 0
cc 3
nc 4
nop 1
crap 3
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
use Dimsav\Translatable\Exception\LocalesNotDefinedException;
11
12
trait Translatable
13
{
14
    protected static $autoloadTranslations = null;
15
16
    protected $defaultLocale;
17
18
    /**
19
     * Alias for getTranslation().
20
     *
21
     * @param string|null $locale
22
     * @param bool        $withFallback
23
     *
24
     * @return \Illuminate\Database\Eloquent\Model|null
25
     */
26 52
    public function translate($locale = null, $withFallback = false)
27
    {
28 52
        return $this->getTranslation($locale, $withFallback);
29
    }
30
31
    /**
32
     * Alias for getTranslation().
33
     *
34
     * @param string $locale
35
     *
36
     * @return \Illuminate\Database\Eloquent\Model|null
37
     */
38 4
    public function translateOrDefault($locale = null)
39
    {
40 4
        return $this->getTranslation($locale, true);
41
    }
42
43
    /**
44
     * Alias for getTranslationOrNew().
45
     *
46
     * @param string $locale
47
     *
48
     * @return \Illuminate\Database\Eloquent\Model|null
49
     */
50 4
    public function translateOrNew($locale = null)
51
    {
52 4
        return $this->getTranslationOrNew($locale);
53
    }
54
55
    /**
56
     * @param string|null $locale
57
     * @param bool        $withFallback
58
     *
59
     * @return \Illuminate\Database\Eloquent\Model|null
60
     */
61 248
    public function getTranslation($locale = null, $withFallback = null)
62
    {
63 248
        $configFallbackLocale = $this->getFallbackLocale();
64 248
        $locale = $locale ?: $this->locale();
65 248
        $withFallback = $withFallback === null ? $this->useFallback() : $withFallback;
66 248
        $fallbackLocale = $this->getFallbackLocale($locale);
67
68 248
        if ($translation = $this->getTranslationByLocaleKey($locale)) {
69 140
            return $translation;
70
        }
71 168
        if ($withFallback && $fallbackLocale) {
72 28
            if ($translation = $this->getTranslationByLocaleKey($fallbackLocale)) {
73 16
                return $translation;
74
            }
75 12
            if ($fallbackLocale !== $configFallbackLocale && $translation = $this->getTranslationByLocaleKey($configFallbackLocale)) {
76 8
                return $translation;
77
            }
78
        }
79
80 164
        return null;
81
    }
82
83
    /**
84
     * @param string|null $locale
85
     *
86
     * @return bool
87
     */
88 12
    public function hasTranslation($locale = null)
89
    {
90 12
        $locale = $locale ?: $this->locale();
91
92 12
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean autoloadTranslations?

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...
93 4
            if ($translation->getAttribute($this->getLocaleKey()) == $locale) {
94 4
                return true;
95
            }
96
        }
97
98 12
        return false;
99
    }
100
101
    /**
102
     * @return string
103
     */
104 312
    public function getTranslationModelName()
105
    {
106 312
        return $this->translationModel ?: $this->getTranslationModelNameDefault();
0 ignored issues
show
Bug introduced by
The property translationModel 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...
107
    }
108
109
    /**
110
     * @return string
111
     */
112 304
    public function getTranslationModelNameDefault()
113
    {
114 304
        $modelName = get_class($this);
115
116 304
        if ($namespace = $this->getTranslationModelNamespace()) {
117 4
            $modelName = $namespace.'\\'.class_basename(get_class($this));
118
        }
119
120 304
        return $modelName.config('translatable.translation_suffix', 'Translation');
121
    }
122
123
    /**
124
     * @return string|null
125
     */
126 304
    public function getTranslationModelNamespace()
127
    {
128 304
        return config('translatable.translation_model_namespace');
129
    }
130
131
    /**
132
     * @return string
133
     */
134 312
    public function getRelationKey()
135
    {
136 312
        if ($this->translationForeignKey) {
137 24
            $key = $this->translationForeignKey;
0 ignored issues
show
Bug introduced by
The property translationForeignKey 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...
138 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...
139
            $key = $this->primaryKey;
140
        } else {
141 292
            $key = $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...
142
        }
143
144 312
        return $key;
145
    }
146
147
    /**
148
     * @return string
149
     */
150 292
    public function getLocaleKey()
151
    {
152 292
        return $this->localeKey ?: config('translatable.locale_key', 'locale');
0 ignored issues
show
Bug introduced by
The property localeKey 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...
153
    }
154
155
    /**
156
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
157
     */
158 292
    public function translations()
159
    {
160 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...
161
    }
162
163
    /**
164
     * @return bool
165
     */
166 12
    private function usePropertyFallback()
167
    {
168 12
        return $this->useFallback() && config('translatable.use_property_fallback', false);
169
    }
170
171
    /**
172
     * Returns the attribute value from fallback translation if value of attribute
173
     * is empty and the property fallback is enabled in the configuration.
174
     * in model.
175
     * @param $locale
176
     * @param $attribute
177
     * @return mixed
178
     */
179 92
    private function getAttributeOrFallback($locale, $attribute)
180
    {
181 92
        $translation = $this->getTranslation($locale);
182
183
        if (
184
            (
185 92
                ! $translation instanceof Model ||
186 92
                empty($translation->$attribute)
187
            ) &&
188 92
            $this->usePropertyFallback()
189
        ) {
190 8
            $translation = $this->getTranslation($this->getFallbackLocale(), false);
191
        }
192
193 92
        if ($translation instanceof Model) {
194 88
            return $translation->$attribute;
195
        }
196
197 8
        return null;
198
    }
199
200
    /**
201
     * @param string $key
202
     *
203
     * @return mixed
204
     */
205 476
    public function getAttribute($key)
206
    {
207 476
        [$attribute, $locale] = $this->getAttributeAndLocale($key);
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $locale does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
208
209 476
        if ($this->isTranslationAttribute($attribute)) {
210 72
            if ($this->getTranslation($locale) === null) {
211 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...
212
            }
213
214
            // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue
215
            // on it. This way, we can use Eloquent's checking for Mutation, type casting, and
216
            // Date fields.
217 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...
218 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...
219
220 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...
221
            }
222
223 56
            return $this->getAttributeOrFallback($locale, $attribute);
224
        }
225
226 476
        return parent::getAttribute($key);
227
    }
228
229
    /**
230
     * @param string $key
231
     * @param mixed  $value
232
     *
233
     * @return $this
234
     */
235 476
    public function setAttribute($key, $value)
236
    {
237 476
        [$attribute, $locale] = $this->getAttributeAndLocale($key);
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $locale does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
238
239 476
        if ($this->isTranslationAttribute($attribute)) {
240 40
            $this->getTranslationOrNew($locale)->$attribute = $value;
241
        } else {
242 476
            return parent::setAttribute($key, $value);
243
        }
244
245 40
        return $this;
246
    }
247
248
    /**
249
     * @param array $options
250
     *
251
     * @return bool
252
     */
253 476
    public function save(array $options = [])
254
    {
255 476
        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...
256
            // If $this->exists and not dirty, parent::save() skips saving and returns
257
            // false. So we have to save the translations
258 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...
259
                return false;
260
            }
261
262 24
            if ($saved = $this->saveTranslations()) {
263 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...
264 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...
265
            }
266
267 24
            return $saved;
268
        }
269
270
        // We save the translations only if the instance is saved in the database.
271 476
        if (parent::save($options)) {
272 476
            return $this->saveTranslations();
273
        }
274
275 8
        return false;
276
    }
277
278
    /**
279
     * @param string $locale
280
     *
281
     * @return \Illuminate\Database\Eloquent\Model
282
     */
283 140
    protected function getTranslationOrNew($locale = null)
284
    {
285 140
        $locale = $locale ?: $this->locale();
286
287 140
        if (($translation = $this->getTranslation($locale, false)) === null) {
288 124
            $translation = $this->getNewTranslation($locale);
289
        }
290
291 140
        return $translation;
292
    }
293
294
    /**
295
     * @param array $attributes
296
     *
297
     * @throws \Illuminate\Database\Eloquent\MassAssignmentException
298
     * @return $this
299
     */
300 476
    public function fill(array $attributes)
301
    {
302 476
        foreach ($attributes as $key => $values) {
303 112
            if ($this->isKeyALocale($key)) {
304 48
                $this->getTranslationOrNew($key)->fill($values);
305 40
                unset($attributes[$key]);
306
            } else {
307 100
                [$attribute, $locale] = $this->getAttributeAndLocale($key);
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you mean $attributes?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $locale does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
308 100
                if ($this->isTranslationAttribute($attribute) and $this->isKeyALocale($locale)) {
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you mean $attributes?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
309 48
                    $this->getTranslationOrNew($locale)->fill([$attribute => $values]);
0 ignored issues
show
Bug introduced by
The variable $attribute does not exist. Did you mean $attributes?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
310 48
                    unset($attributes[$key]);
311
                }
312
            }
313
        }
314
315 476
        return parent::fill($attributes);
316
    }
317
318
    /**
319
     * @param string $key
320
     */
321 248
    private function getTranslationByLocaleKey($key)
322
    {
323 248
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean autoloadTranslations?

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...
324 196
            if ($translation->getAttribute($this->getLocaleKey()) == $key) {
325 164
                return $translation;
326
            }
327
        }
328
329 168
        return null;
330
    }
331
332
    /**
333
     * @param null $locale
334
     *
335
     * @return string
336
     */
337 252
    private function getFallbackLocale($locale = null)
338
    {
339 252
        if ($locale && $this->isLocaleCountryBased($locale)) {
340 28
            if ($fallback = $this->getLanguageFromCountryBasedLocale($locale)) {
341 28
                return $fallback;
342
            }
343
        }
344
345 252
        return config('translatable.fallback_locale');
346
    }
347
348
    private function isLocaleCountryBased(string $locale): bool
349
    {
350
        return $this->getLocalesHelper()->isLocaleCountryBased($locale);
351
    }
352
353 248
    private function getLanguageFromCountryBasedLocale(string $locale): string
354
    {
355 248
        return $this->getLocalesHelper()->getLanguageFromCountryBasedLocale($locale);
356
    }
357
358
    /**
359
     * @return bool|null
360
     */
361
    private function useFallback()
362
    {
363 28
        if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) {
364
            return $this->useTranslationFallback;
0 ignored issues
show
Bug introduced by
The property useTranslationFallback 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...
365 28
        }
366
367 28
        return config('translatable.use_fallback');
368
    }
369
370
    /**
371
     * @param string $key
372
     *
373 144
     * @return bool
374
     */
375 144
    public function isTranslationAttribute($key)
376 12
    {
377
        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...
378
    }
379 132
380
    protected function isKeyALocale(string $key): bool
381
    {
382
        return $this->getLocalesHelper()->has($key);
383
    }
384
385
    protected function getLocales(): array
386
    {
387 476
        return $this->getLocalesHelper()->all();
388
    }
389 476
390
    protected function getLocaleSeparator(): string
391
    {
392
        return $this->getLocalesHelper()->getLocaleSeparator();
393
    }
394
395
    /**
396
     * @return bool
397
     */
398 112
    protected function saveTranslations()
399
    {
400 112
        $saved = true;
401
402 108
        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...
403
            return $saved;
404
        }
405
406
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean autoloadTranslations?

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...
407
            if ($saved && $this->isTranslationDirty($translation)) {
408
                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...
409 112
                    $translation->setConnection($connectionName);
410
                }
411 112
412
                $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...
413 112
                $saved = $translation->save();
414 4
            }
415 4
        }
416
417
        return $saved;
418 108
    }
419 108
420 108
    /**
421 20
     * @param array
422 20
     *
423 20
     * @return \Illuminate\Database\Eloquent\Model
424
     */
425
    public function replicateWithTranslations(array $except = null)
426 100
    {
427
        $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...
428
429
        unset($newInstance->translations);
430 108
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean autoloadTranslations?

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...
431
            $newTranslation = $translation->replicate();
432
            $newInstance->translations->add($newTranslation);
433
        }
434
435
        return  $newInstance;
436 248
    }
437
438 248
    /**
439
     * @param \Illuminate\Database\Eloquent\Model $translation
440
     *
441
     * @return bool
442
     */
443
    protected function isTranslationDirty(Model $translation)
444 476
    {
445
        $dirtyAttributes = $translation->getDirty();
446 476
        unset($dirtyAttributes[$this->getLocaleKey()]);
447
448 476
        return count($dirtyAttributes) > 0;
449 476
    }
450
451
    /**
452 100
     * @param string $locale
453 100
     *
454 100
     * @return \Illuminate\Database\Eloquent\Model
455 100
     */
456
    public function getNewTranslation($locale)
457
    {
458 100
        $modelName = $this->getTranslationModelName();
459 100
        $translation = new $modelName();
460
        $translation->setAttribute($this->getLocaleKey(), $locale);
461
        $this->translations->add($translation);
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean autoloadTranslations?

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...
462
463 96
        return $translation;
464
    }
465
466
    /**
467
     * @param $key
468
     *
469
     * @return bool
470
     */
471 4
    public function __isset($key)
472
    {
473 4
        return $this->isTranslationAttribute($key) || parent::__isset($key);
474
    }
475 4
476 4
    /**
477 4
     * @param \Illuminate\Database\Eloquent\Builder $query
478 4
     * @param string                                $locale
479
     *
480
     * @return \Illuminate\Database\Eloquent\Builder|static
481 4
     */
482
    public function scopeTranslatedIn(Builder $query, $locale = null)
483
    {
484
        $locale = $locale ?: $this->locale();
485
486
        return $query->whereHas('translations', function (Builder $q) use ($locale) {
487
            $q->where($this->getLocaleKey(), '=', $locale);
488
        });
489 100
    }
490
491 100
    /**
492 100
     * @param \Illuminate\Database\Eloquent\Builder $query
493
     * @param string                                $locale
494 100
     *
495
     * @return \Illuminate\Database\Eloquent\Builder|static
496
     */
497
    public function scopeNotTranslatedIn(Builder $query, $locale = null)
498
    {
499
        $locale = $locale ?: $this->locale();
500
501
        return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) {
502 128
            $q->where($this->getLocaleKey(), '=', $locale);
503
        });
504 128
    }
505 128
506 128
    /**
507 128
     * @param \Illuminate\Database\Eloquent\Builder $query
508
     *
509 128
     * @return \Illuminate\Database\Eloquent\Builder|static
510
     */
511
    public function scopeTranslated(Builder $query)
512
    {
513
        return $query->has('translations');
514
    }
515
516
    /**
517 148
     * Adds scope to get a list of translated attributes, using the current locale.
518
     * Example usage: Country::listsTranslations('name')->get()->toArray()
519 148
     * Will return an array with items:
520
     *  [
521
     *      'id' => '1',                // The id of country
522
     *      'name' => 'Griechenland'    // The translated name
523
     *  ].
524
     *
525
     * @param \Illuminate\Database\Eloquent\Builder $query
526
     * @param string                                $translationField
527
     */
528 8
    public function scopeListsTranslations(Builder $query, $translationField)
529
    {
530 8
        $withFallback = $this->useFallback();
531
        $translationTable = $this->getTranslationsTable();
532
        $localeKey = $this->getLocaleKey();
533 8
534 8
        $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...
535
            ->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...
536
            ->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...
537
            ->where($translationTable.'.'.$localeKey, $this->locale());
538
        if ($withFallback) {
539
            $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) {
540
                $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale())
541
                  ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use (
542
                      $translationTable,
543 8
                      $localeKey
544
                  ) {
545 8
                      $q->select($translationTable.'.'.$this->getRelationKey())
546
                        ->from($translationTable)
547
                        ->where($translationTable.'.'.$localeKey, $this->locale());
548 8
                  });
549 8
            });
550
        }
551
    }
552
553
    /**
554
     * This scope eager loads the translations for the default and the fallback locale only.
555
     * We can use this as a shortcut to improve performance in our application.
556
     *
557 4
     * @param Builder $query
558
     */
559 4
    public function scopeWithTranslation(Builder $query)
560
    {
561
        $query->with([
562
            'translations' => function (Relation $query) {
563
                if ($this->useFallback()) {
564
                    $locale = $this->locale();
565
                    $countryFallbackLocale = $this->getFallbackLocale($locale); // e.g. de-DE => de
566
                    $locales = array_unique([$locale, $countryFallbackLocale, $this->getFallbackLocale()]);
567
568
                    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...
569
                }
570
571
                return $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale());
572
            },
573
        ]);
574 12
    }
575
576 12
    /**
577 12
     * This scope filters results by checking the translation fields.
578 12
     *
579
     * @param \Illuminate\Database\Eloquent\Builder $query
580
     * @param string                                $key
581 12
     * @param string                                $value
582 12
     * @param string                                $locale
583 12
     *
584 12
     * @return \Illuminate\Database\Eloquent\Builder|static
585
     */
586 4
    public function scopeWhereTranslation(Builder $query, $key, $value, $locale = null)
587
    {
588 4
        return $query->whereHas('translations', function (Builder $query) use ($key, $value, $locale) {
589 4
            $query->where($this->getTranslationsTable().'.'.$key, $value);
590
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
591 4
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
592 4
            }
593 4
        });
594 4
    }
595 4
596
    /**
597 12
     * This scope filters results by checking the translation fields.
598
     *
599
     * @param \Illuminate\Database\Eloquent\Builder $query
600
     * @param string                                $key
601
     * @param string                                $value
602
     * @param string                                $locale
603
     *
604
     * @return \Illuminate\Database\Eloquent\Builder|static
605 12
     */
606
    public function scopeOrWhereTranslation(Builder $query, $key, $value, $locale = null)
607 12
    {
608
        return $query->orWhereHas('translations', function (Builder $query) use ($key, $value, $locale) {
609 12
            $query->where($this->getTranslationsTable().'.'.$key, $value);
610 8
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
611 8
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
612 8
            }
613
        });
614 8
    }
615
616
    /**
617 4
     * This scope filters results by checking the translation fields.
618 12
     *
619
     * @param \Illuminate\Database\Eloquent\Builder $query
620 12
     * @param string                                $key
621
     * @param string                                $value
622
     * @param string                                $locale
623
     *
624
     * @return \Illuminate\Database\Eloquent\Builder|static
625
     */
626
    public function scopeWhereTranslationLike(Builder $query, $key, $value, $locale = null)
627
    {
628
        return $query->whereHas('translations', function (Builder $query) use ($key, $value, $locale) {
629
            $query->where($this->getTranslationsTable().'.'.$key, 'LIKE', $value);
630
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
631
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
632 12
            }
633
        });
634
    }
635 12
636 12
    /**
637 4
     * This scope filters results by checking the translation fields.
638
     *
639 12
     * @param \Illuminate\Database\Eloquent\Builder $query
640
     * @param string                                $key
641
     * @param string                                $value
642
     * @param string                                $locale
643
     *
644
     * @return \Illuminate\Database\Eloquent\Builder|static
645
     */
646
    public function scopeOrWhereTranslationLike(Builder $query, $key, $value, $locale = null)
647
    {
648
        return $query->orWhereHas('translations', function (Builder $query) use ($key, $value, $locale) {
649
            $query->where($this->getTranslationsTable().'.'.$key, 'LIKE', $value);
650
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
651
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
652 4
            }
653
        });
654
    }
655 4
656 4
    /**
657
     * This scope sorts results by the given translation field.
658
     *
659 4
     * @param \Illuminate\Database\Eloquent\Builder $query
660
     * @param string                                $key
661
     * @param string                                $sortmethod
662
     *
663
     * @return \Illuminate\Database\Eloquent\Builder|static
664
     */
665
    public function scopeOrderByTranslation(Builder $query, $key, $sortmethod = 'asc')
666
    {
667
        $translationTable = $this->getTranslationsTable();
668
        $localeKey = $this->getLocaleKey();
669
        $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...
670
        $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...
671
672 12
        return $query
673
            ->join($translationTable, function (JoinClause $join) use ($translationTable, $localeKey, $table, $keyName) {
674
                $join
675 12
                    ->on($translationTable.'.'.$this->getRelationKey(), '=', $table.'.'.$keyName)
676 12
                    ->where($translationTable.'.'.$localeKey, $this->locale());
677 4
            })
678
            ->orderBy($translationTable.'.'.$key, $sortmethod)
679 12
            ->select($table.'.*')
680
            ->with('translations');
681
    }
682
683
    /**
684
     * @return array
685
     */
686
    public function attributesToArray()
687
    {
688
        $attributes = parent::attributesToArray();
689
690
        if (
691
            (! $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...
692 4
            || self::$autoloadTranslations === false
693
        ) {
694
            return $attributes;
695 4
        }
696 4
697
        $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...
698
699 4
        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...
700
            if (in_array($field, $hiddenAttributes)) {
701
                continue;
702
            }
703
704
            $attributes[$field] = $this->getAttributeOrFallback(null, $field);
705
        }
706
707
        return $attributes;
708
    }
709
710
    /**
711 8
     * @return array
712
     */
713 8
    public function getTranslationsArray()
714 8
    {
715 8
        $translations = [];
716 8
717
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean autoloadTranslations?

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...
718
            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...
719
                $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr};
720
            }
721 8
        }
722 8
723 8
        return $translations;
724 8
    }
725 8
726 8
    /**
727
     * @return string
728
     */
729
    private function getTranslationsTable()
730
    {
731
        return app()->make($this->getTranslationModelName())->getTable();
732 48
    }
733
734 48
    protected function locale(): string
735
    {
736
        if ($this->defaultLocale) {
737 48
            return $this->defaultLocale;
738 48
        }
739
740 16
        return $this->getLocalesHelper()->current();
741
    }
742
743 32
    /**
744
     * Set the default locale on the model.
745 32
     *
746 32
     * @param $locale
747 4
     *
748
     * @return $this
749
     */
750 32
    public function setDefaultLocale($locale)
751
    {
752
        $this->defaultLocale = $locale;
753 32
754
        return $this;
755
    }
756
757
    /**
758
     * Get the default locale on the model.
759 4
     *
760
     * @return mixed
761 4
     */
762
    public function getDefaultLocale()
763 4
    {
764 4
        return $this->defaultLocale;
765 4
    }
766
767
    /**
768
     * Deletes all translations for this model.
769 4
     *
770
     * @param string|array|null $locales The locales to be deleted (array or single string)
771
     *                                   (e.g., ["en", "de"] would remove these translations).
772
     */
773
    public function deleteTranslations($locales = null)
774
    {
775 56
        if ($locales === null) {
776
            $translations = $this->translations()->get();
777 56
        } else {
778
            $locales = (array) $locales;
779
            $translations = $this->translations()->whereIn($this->getLocaleKey(), $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...
780
        }
781
        foreach ($translations as $translation) {
782
            $translation->delete();
783 476
        }
784
785 476
        // we need to manually "reload" the collection built from the relationship
786 4
        // otherwise $this->translations()->get() would NOT be the same as $this->translations
787
        $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...
788
    }
789 476
790 476
    /**
791
     * @param $key
792
     *
793
     * @return array
794
     */
795
    private function getAttributeAndLocale($key)
796
    {
797
        if (str_contains($key, ':')) {
0 ignored issues
show
Deprecated Code introduced by
The function str_contains() has been deprecated with message: Str::contains() should be used directly instead. Will be removed in Laravel 5.9.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
798
            return explode(':', $key);
799
        }
800 4
801
        return [$key, $this->locale()];
802 4
    }
803
804 4
    /**
805
     * @return bool
806
     */
807
    private function toArrayAlwaysLoadsTranslations()
808
    {
809
        return config('translatable.to_array_always_loads_translations', true);
810
    }
811
812
    public static function enableAutoloadTranslations()
813
    {
814
        self::$autoloadTranslations = true;
815
    }
816
817
    public static function defaultAutoloadTranslations()
818
    {
819
        self::$autoloadTranslations = null;
820
    }
821
822
    public static function disableAutoloadTranslations()
823 12
    {
824
        self::$autoloadTranslations = false;
825 12
    }
826 4
827
    protected function getLocalesHelper(): Locales
828 8
    {
829 8
        return app(Locales::class);
830
    }
831
}
832