Passed
Pull Request — master (#414)
by
unknown
62:57
created

Translatable::locale()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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...
95
            if ($translation->getAttribute($this->getLocaleKey()) == $locale) {
96
                return true;
97
            }
98
        }
99
100
        return false;
101
    }
102
103
    /**
104
     * @return string
105
     */
106
    public function getTranslationModelName() {
107
        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...
108
    }
109
110
    /**
111
     * @return string
112
     */
113
    public function getTranslationModelNameDefault() {
114
        return get_class($this).$this->translationSuffix ?: $this->defaultTranslationSuffix;
0 ignored issues
show
Bug introduced by
The property translationSuffix does not seem to exist. Did you mean defaultTranslationSuffix?

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...
115
    }
116
117
    /**
118
     * @return string
119
     */
120
    public function getRelationKey() {
121
        if ($this->translationForeignKey) {
122
            $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...
123
        } 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...
124
            $key = $this->primaryKey;
125
        } else {
126
            $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...
127
        }
128
129
        return $key;
130
    }
131
132
    /**
133
     * @return string
134
     */
135
    public function getLocaleKey() {
136
        return $this->localeKey ?: $this->defaultLocaleKey;
0 ignored issues
show
Bug introduced by
The property localeKey does not seem to exist. Did you mean locale?

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...
137
    }
138
139
    /**
140
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
141
     */
142
    public function translations() {
143
        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...
144
    }
145
146
    /**
147
     * @return bool
148
     */
149
    private function usePropertyFallback() {
150
        return $this->usePropertyFallback;
151
    }
152
153
    /**
154
     * Returns the attribute value from fallback translation if value of attribute
155
     * is empty and the property fallback is enabled in the configuration.
156
     * in model.
157
     * @param $locale
158
     * @param $attribute
159
     * @return mixed
160
     */
161
    private function getAttributeOrFallback($locale, $attribute) {
162
        $value = $this->getTranslation($locale)->$attribute;
163
164
        $usePropertyFallback = $this->useFallback() && $this->usePropertyFallback();
165
        if (empty($value) && $usePropertyFallback) {
166
            return $this->getTranslation($this->getFallbackLocale(), true)->$attribute;
167
        }
168
169
        return $value;
170
    }
171
172
    /**
173
     * @param string $key
174
     *
175
     * @return mixed
176
     */
177
    public function getAttribute($key) {
178
        list($attribute, $locale) = $this->getAttributeAndLocale($key);
179
180
        if ($this->isTranslationAttribute($attribute)) {
181
            if ($this->getTranslation($locale) === null) {
182
                return null;
183
            }
184
185
            // If the given $attribute has a mutator, we push it to $attributes and then call getAttributeValue
186
            // on it. This way, we can use Eloquent's checking for Mutation, type casting, and
187
            // Date fields.
188
            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...
189
                $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...
190
191
                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...
192
            }
193
194
            return $this->getAttributeOrFallback($locale, $attribute);
195
        }
196
197
        return parent::getAttribute($key);
198
    }
199
200
    /**
201
     * @param string $key
202
     * @param mixed  $value
203
     *
204
     * @return $this
205
     */
206
    public function setAttribute($key, $value) {
207
        list($attribute, $locale) = $this->getAttributeAndLocale($key);
208
209
        if ($this->isTranslationAttribute($attribute)) {
210
            $this->getTranslationOrNew($locale)->$attribute = $value;
211
        } else {
212
            return parent::setAttribute($key, $value);
213
        }
214
215
        return $this;
216
    }
217
218
    /**
219
     * @param array $options
220
     *
221
     * @return bool
222
     */
223
    public function save(array $options = []) {
224
        if ($this->exists) {
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...
225
            if (count($this->getDirty()) > 0) {
0 ignored issues
show
Bug introduced by
It seems like getDirty() 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...
226
                // If $this->exists and dirty, parent::save() has to return true. If not,
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
227
                // an error has occurred. Therefore we shouldn't save the translations.
228
                if (parent::save($options)) {
229
                    return $this->saveTranslations();
230
                }
231
232
                return false;
233
            } else {
234
                // If $this->exists and not dirty, parent::save() skips saving and returns
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
235
                // false. So we have to save the translations
236
                if ($saved = $this->saveTranslations()) {
237
                    $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...
238
                    $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...
239
                }
240
241
                return $saved;
242
            }
243
        } elseif (parent::save($options)) {
244
            // We save the translations only if the instance is saved in the database.
245
            return $this->saveTranslations();
246
        }
247
248
        return false;
249
    }
250
251
    /**
252
     * @param string $locale
253
     *
254
     * @return \Illuminate\Database\Eloquent\Model|null
255
     */
256
    protected function getTranslationOrNew($locale) {
257
        if (($translation = $this->getTranslation($locale, false)) === null) {
258
            $translation = $this->getNewTranslation($locale);
259
        }
260
261
        return $translation;
262
    }
263
264
    /**
265
     * @param array $attributes
266
     *
267
     * @throws \Illuminate\Database\Eloquent\MassAssignmentException
268
     * @return $this
269
     */
270
    public function fill(array $attributes) {
271
        foreach ($attributes as $key => $values) {
272
            if ($this->isKeyALocale($key)) {
273
                $this->getTranslationOrNew($key)->fill($values);
274
                unset($attributes[$key]);
275
            } else {
276
                list($attribute, $locale) = $this->getAttributeAndLocale($key);
277
                if ($this->isTranslationAttribute($attribute) and $this->isKeyALocale($locale)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
278
                    $this->getTranslationOrNew($locale)->fill([$attribute => $values]);
279
                    unset($attributes[$key]);
280
                }
281
            }
282
        }
283
284
        return parent::fill($attributes);
285
    }
286
287
    /**
288
     * @param string $key
289
     */
290
    private function getTranslationByLocaleKey($key) {
291
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean defaultTranslationSuffix?

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...
292
            if ($translation->getAttribute($this->getLocaleKey()) == $key) {
293
                return $translation;
294
            }
295
        }
296
297
        return null;
298
    }
299
300
    /**
301
     * @param null $locale
302
     *
303
     * @return string
304
     */
305
    private function getFallbackLocale($locale = null) {
306
        if ($locale && $this->isLocaleCountryBased($locale)) {
307
            if ($fallback = $this->getLanguageFromCountryBasedLocale($locale)) {
308
                return $fallback;
309
            }
310
        }
311
312
        return $this->fallbackLocale;
313
    }
314
315
    /**
316
     * @param $locale
317
     *
318
     * @return bool
319
     */
320
    private function isLocaleCountryBased($locale) {
321
        return strpos($locale, $this->getLocaleSeparator()) !== false;
322
    }
323
324
    /**
325
     * @param $locale
326
     *
327
     * @return string
328
     */
329
    private function getLanguageFromCountryBasedLocale($locale) {
330
        $parts = explode($this->getLocaleSeparator(), $locale);
331
332
        return array_get($parts, 0);
333
    }
334
335
    /**
336
     * @return bool|null
337
     */
338
    private function useFallback() {
339
        if (isset($this->useTranslationFallback) && $this->useTranslationFallback !== null) {
340
            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...
341
        }
342
343
        return $this->useFallback;
344
    }
345
346
    /**
347
     * @param string $key
348
     *
349
     * @return bool
350
     */
351
    public function isTranslationAttribute($key) {
352
        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...
353
    }
354
355
    /**
356
     * @param string $key
357
     *
358
     * @throws \Dimsav\Translatable\Exception\LocalesNotDefinedException
359
     * @return bool
360
     */
361
    protected function isKeyALocale($key) {
362
        $locales = $this->getLocales();
363
364
        return in_array($key, $locales);
365
    }
366
367
    /**
368
     * @throws \Dimsav\Translatable\Exception\LocalesNotDefinedException
369
     * @return array
370
     */
371
    protected function getLocales() {
372
        $localesConfig = (array) $this->locales;
373
374
        if (empty($localesConfig)) {
375
            throw new LocalesNotDefinedException('Please make sure you have run "php artisan config:publish dimsav/laravel-translatable" '.
376
                ' and that the locales configuration is defined.');
377
        }
378
379
        $locales = [];
380
        foreach ($localesConfig as $key => $locale) {
381
            if (is_array($locale)) {
382
                $locales[] = $key;
383
                foreach ($locale as $countryLocale) {
384
                    $locales[] = $key.$this->getLocaleSeparator().$countryLocale;
385
                }
386
            } else {
387
                $locales[] = $locale;
388
            }
389
        }
390
391
        return $locales;
392
    }
393
394
    /**
395
     * @return string
396
     */
397
    protected function getLocaleSeparator() {
398
        return $this->localeSeparator;
399
    }
400
401
    /**
402
     * @return bool
403
     */
404
    protected function saveTranslations() {
405
        $saved = true;
406
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean defaultTranslationSuffix?

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

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

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...
459
460
        return $translation;
461
    }
462
463
    /**
464
     * @param $key
465
     *
466
     * @return bool
467
     */
468
    public function __isset($key) {
469
        return $this->isTranslationAttribute($key) || parent::__isset($key);
470
    }
471
472
    /**
473
     * @param \Illuminate\Database\Eloquent\Builder $query
474
     * @param string                                $locale
475
     *
476
     * @return \Illuminate\Database\Eloquent\Builder|static
477
     */
478 View Code Duplication
    public function scopeTranslatedIn(Builder $query, $locale = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
479
        $locale = $locale ?: $this->locale();
480
481
        return $query->whereHas('translations', function (Builder $q) use ($locale) {
482
            $q->where($this->getLocaleKey(), '=', $locale);
483
        });
484
    }
485
486
    /**
487
     * @param \Illuminate\Database\Eloquent\Builder $query
488
     * @param string                                $locale
489
     *
490
     * @return \Illuminate\Database\Eloquent\Builder|static
491
     */
492 View Code Duplication
    public function scopeNotTranslatedIn(Builder $query, $locale = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
493
        $locale = $locale ?: $this->locale();
494
495
        return $query->whereDoesntHave('translations', function (Builder $q) use ($locale) {
496
            $q->where($this->getLocaleKey(), '=', $locale);
497
        });
498
    }
499
500
    /**
501
     * @param \Illuminate\Database\Eloquent\Builder $query
502
     *
503
     * @return \Illuminate\Database\Eloquent\Builder|static
504
     */
505
    public function scopeTranslated(Builder $query) {
506
        return $query->has('translations');
507
    }
508
509
    /**
510
     * Adds scope to get a list of translated attributes, using the current locale.
511
     * Example usage: Country::listsTranslations('name')->get()->toArray()
512
     * Will return an array with items:
513
     *  [
514
     *      'id' => '1',                // The id of country
515
     *      'name' => 'Griechenland'    // The translated name
516
     *  ].
517
     *
518
     * @param \Illuminate\Database\Eloquent\Builder $query
519
     * @param string                                $translationField
520
     */
521
    public function scopeListsTranslations(Builder $query, $translationField) {
522
        $withFallback = $this->useFallback();
523
        $translationTable = $this->getTranslationsTable();
524
        $localeKey = $this->getLocaleKey();
525
526
        $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...
527
            ->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...
528
            ->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...
529
            ->where($translationTable.'.'.$localeKey, $this->locale());
530
        if ($withFallback) {
531
            $query->orWhere(function (Builder $q) use ($translationTable, $localeKey) {
532
                $q->where($translationTable.'.'.$localeKey, $this->getFallbackLocale())
533
                  ->whereNotIn($translationTable.'.'.$this->getRelationKey(), function (QueryBuilder $q) use (
534
                      $translationTable,
535
                      $localeKey
536
                  ) {
537
                      $q->select($translationTable.'.'.$this->getRelationKey())
538
                        ->from($translationTable)
539
                        ->where($translationTable.'.'.$localeKey, $this->locale());
540
                  });
541
            });
542
        }
543
    }
544
545
    /**
546
     * This scope eager loads the translations for the default and the fallback locale only.
547
     * We can use this as a shortcut to improve performance in our application.
548
     *
549
     * @param Builder $query
550
     */
551
    public function scopeWithTranslation(Builder $query) {
552
        $query->with([
553
            'translations' => function (Relation $query) {
554
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->locale());
555
556
                if ($this->useFallback()) {
557
                    return $query->orWhere($this->getTranslationsTable().'.'.$this->getLocaleKey(), $this->getFallbackLocale());
558
                }
559
            },
560
        ]);
561
    }
562
563
    /**
564
     * This scope filters results by checking the translation fields.
565
     *
566
     * @param \Illuminate\Database\Eloquent\Builder $query
567
     * @param string                                $key
568
     * @param string                                $value
569
     * @param string                                $locale
570
     *
571
     * @return \Illuminate\Database\Eloquent\Builder|static
572
     */
573 View Code Duplication
    public function scopeWhereTranslation(Builder $query, $key, $value, $locale = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
574
        return $query->whereHas('translations', function (Builder $query) use ($key, $value, $locale) {
575
            $query->where($this->getTranslationsTable().'.'.$key, $value);
576
            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...
577
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
578
            }
579
        });
580
    }
581
582
    /**
583
     * This scope filters results by checking the translation fields.
584
     *
585
     * @param \Illuminate\Database\Eloquent\Builder $query
586
     * @param string                                $key
587
     * @param string                                $value
588
     * @param string                                $locale
589
     *
590
     * @return \Illuminate\Database\Eloquent\Builder|static
591
     */
592
    public function scopeOrWhereTranslation(Builder $query, $key, $value, $locale = null) {
593
        return $query->orWhereHas('translations', function (Builder $query) use ($key, $value, $locale) {
594
            $query->where($this->getTranslationsTable().'.'.$key, $value);
595
            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...
596
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), $locale);
597
            }
598
        });
599
    }
600
601
    /**
602
     * This scope filters results by checking the translation fields.
603
     *
604
     * @param \Illuminate\Database\Eloquent\Builder $query
605
     * @param string                                $key
606
     * @param string                                $value
607
     * @param string                                $locale
608
     *
609
     * @return \Illuminate\Database\Eloquent\Builder|static
610
     */
611 View Code Duplication
    public function scopeWhereTranslationLike(Builder $query, $key, $value, $locale = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
612
        return $query->whereHas('translations', function (Builder $query) use ($key, $value, $locale) {
613
            $query->where($this->getTranslationsTable().'.'.$key, 'LIKE', $value);
614
            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...
615
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
616
            }
617
        });
618
    }
619
620
    /**
621
     * This scope filters results by checking the translation fields.
622
     *
623
     * @param \Illuminate\Database\Eloquent\Builder $query
624
     * @param string                                $key
625
     * @param string                                $value
626
     * @param string                                $locale
627
     *
628
     * @return \Illuminate\Database\Eloquent\Builder|static
629
     */
630
    public function scopeOrWhereTranslationLike(Builder $query, $key, $value, $locale = null) {
631
        return $query->orWhereHas('translations', function (Builder $query) use ($key, $value, $locale) {
632
            $query->where($this->getTranslationsTable().'.'.$key, 'LIKE', $value);
633
            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...
634
                $query->where($this->getTranslationsTable().'.'.$this->getLocaleKey(), 'LIKE', $locale);
635
            }
636
        });
637
    }
638
639
    /**
640
     * @return array
641
     */
642
    public function attributesToArray() {
643
        $attributes = parent::attributesToArray();
644
645
        if (! $this->relationLoaded('translations') && ! $this->toArrayAlwaysLoadsTranslations()) {
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...
646
            return $attributes;
647
        }
648
649
        $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...
650
651
        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...
652
            if (in_array($field, $hiddenAttributes)) {
653
                continue;
654
            }
655
656
            if ($translations = $this->getTranslation()) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $translations is correct as $this->getTranslation() (which targets Dimsav\Translatable\Translatable::getTranslation()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
657
                $attributes[$field] = $translations->$field;
658
            }
659
        }
660
661
        return $attributes;
662
    }
663
664
    /**
665
     * @return array
666
     */
667
    public function getTranslationsArray() {
668
        $translations = [];
669
670
        foreach ($this->translations as $translation) {
0 ignored issues
show
Bug introduced by
The property translations does not seem to exist. Did you mean defaultTranslationSuffix?

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...
671
            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...
672
                $translations[$translation->{$this->getLocaleKey()}][$attr] = $translation->{$attr};
673
            }
674
        }
675
676
        return $translations;
677
    }
678
679
    /**
680
     * @return string
681
     */
682
    private function getTranslationsTable() {
683
        $model = $this->getTranslationModelName();
684
        return (new $model)->getTable();
685
    }
686
687
    /**
688
     * @return string
689
     */
690
    protected function locale() {
691
        if ($this->defaultLocale) {
692
            return $this->defaultLocale;
693
        }
694
695
        return $this->locale;
696
    }
697
698
    /**
699
     * Set the default locale on the model.
700
     *
701
     * @param $locale
702
     *
703
     * @return $this
704
     */
705
    public function setDefaultLocale($locale) {
706
        $this->defaultLocale = $locale;
707
708
        return $this;
709
    }
710
711
    /**
712
     * Get the default locale on the model.
713
     *
714
     * @return mixed
715
     */
716
    public function getDefaultLocale() {
717
        return $this->defaultLocale;
718
    }
719
720
    /**
721
     * Deletes all translations for this model.
722
     *
723
     * @param string|array|null $locales The locales to be deleted (array or single string)
724
     *                                   (e.g., ["en", "de"] would remove these translations).
725
     */
726
    public function deleteTranslations($locales = null) {
727
        if ($locales === null) {
728
            $translations = $this->translations()->get();
729
        } else {
730
            $locales = (array) $locales;
731
            $translations = $this->translations()->whereIn($this->getLocaleKey(), $locales)->get();
732
        }
733
        foreach ($translations as $translation) {
734
            $translation->delete();
735
        }
736
737
        // we need to manually "reload" the collection built from the relationship
738
        // otherwise $this->translations()->get() would NOT be the same as $this->translations
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
739
        $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...
740
    }
741
742
    /**
743
     * @param $key
744
     *
745
     * @return array
746
     */
747
    private function getAttributeAndLocale($key) {
748
        if (str_contains($key, ':')) {
749
            return explode(':', $key);
750
        }
751
752
        return [$key, $this->locale()];
753
    }
754
755
    /**
756
     * @return bool
757
     */
758
    private function toArrayAlwaysLoadsTranslations() {
759
        return $this->toArrayAlwaysLoadsTranslations;
760
    }
761
}
762