Passed
Push — ft/package ( 5ee474...180175 )
by Philippe
05:16 queued 12s
created

Translatable::injectTranslationForForm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
ccs 0
cts 5
cp 0
crap 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Thinktomorrow\Chief\Common\Translatable;
4
5
use Illuminate\Support\Facades\DB;
6
use InvalidArgumentException;
7
8
/**
9
 * Trait Translatable
10
 * @author Ben Cavens
11
 *
12
 * Allows the entity to contain multiple translations
13
 * requires the parent entity to include the Dimsav/Translatable/Translatable trait
14
 *
15
 */
16
trait Translatable
17
{
18
    public function getDefaultTranslation($attribute)
19
    {
20
        if (!($translation = $this->getTranslation(config('app.fallback_locale')))) {
0 ignored issues
show
Bug introduced by
The method getTranslation() does not exist on Thinktomorrow\Chief\Comm...anslatable\Translatable. Did you maybe mean getTranslationFor()? ( Ignorable by Annotation )

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

20
        if (!($translation = $this->/** @scrutinizer ignore-call */ getTranslation(config('app.fallback_locale')))) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
21
            return null;
22
        }
23
24
        return $translation->$attribute;
25
    }
26
27
    /**
28
     * Save multiple attributes at once
29
     *
30
     * @param $locale
31
     * @param array $values
32
     */
33 4
    public function updateTranslation($locale, array $values)
34
    {
35 4
        foreach ($values as $attribute => $value) {
36 4
            $this->setTranslation($locale, $attribute, $value);
37
        }
38
39 4
        $this->save();
0 ignored issues
show
Bug introduced by
It seems like save() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

39
        $this->/** @scrutinizer ignore-call */ 
40
               save();
Loading history...
40 4
    }
41
42
    /**
43
     * Save a single attribute
44
     *
45
     * @param $locale
46
     * @param $attribute
47
     * @param $value
48
     */
49
    public function saveTranslation($locale, $attribute, $value)
50
    {
51
        $this->setTranslation($locale, $attribute, $value);
52
53
        $this->save();
54
    }
55
56
    /**
57
     * Get translation for a specific column
58
     *
59
     * @param $attribute
60
     * @param $locale
61
     * @param bool $strict false = use fallback locale, true = no result if locale not present
62
     * @return null
63
     */
64
    public function getTranslationFor($attribute, $locale = null, $strict = true)
65
    {
66
        // No locale given means we take the current defaulted locale (handled automagically)
67
        if (!$locale) {
68
            return $this->getAttribute($attribute);
0 ignored issues
show
Bug introduced by
It seems like getAttribute() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

68
            return $this->/** @scrutinizer ignore-call */ getAttribute($attribute);
Loading history...
69
        }
70
71
        if (!$this->hasTranslation($locale) && $strict) {
0 ignored issues
show
Bug introduced by
The method hasTranslation() does not exist on Thinktomorrow\Chief\Comm...anslatable\Translatable. Did you maybe mean saveTranslation()? ( Ignorable by Annotation )

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

71
        if (!$this->/** @scrutinizer ignore-call */ hasTranslation($locale) && $strict) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
72
            return null;
73
        }
74
75
        return $this->getTranslation($locale)->{$attribute};
76
    }
77
78
    /**
79
     * Create or update a translation attribute.
80
     * Note: only sets to entity, does not save it.
81
     *
82
     * @param $locale
83
     * @param $attribute
84
     * @param $value
85
     */
86 4
    private function setTranslation($locale, $attribute, $value)
87
    {
88 4
        $this->validateLocale($locale);
89
90 4
        $this->translateOrNew($locale)->$attribute = $value;
0 ignored issues
show
Bug introduced by
The method translateOrNew() does not exist on Thinktomorrow\Chief\Comm...anslatable\Translatable. Did you maybe mean translateForForm()? ( Ignorable by Annotation )

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

90
        $this->/** @scrutinizer ignore-call */ 
91
               translateOrNew($locale)->$attribute = $value;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
91 4
    }
92
93
    public function removeTranslation($locale)
94
    {
95
        if (!$this->hasTranslation($locale)) {
96
            return;
97
        }
98
99
        return $this->getTranslation($locale)->delete();
100
    }
101
102 5
    public static function availableLocales()
103
    {
104 5
        return (new self())->getLocales();
0 ignored issues
show
Bug introduced by
It seems like getLocales() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

104
        return (new self())->/** @scrutinizer ignore-call */ getLocales();
Loading history...
105
    }
106
107
    /**
108
     * @deprecated use availableLocales instead
109
     * @return \Illuminate\Config\Repository|mixed
110
     */
111 4
    public static function getAvailableLocales()
112
    {
113 4
        return static::availableLocales();
114
    }
115
116
    /**
117
     * Get all locales where this entity
118
     * already has any translations of
119
     *
120
     * @return array
121
     */
122
    public function getUsedLocales()
123
    {
124
        return $this->fetchLocales(true);
125
    }
126
127
    /**
128
     * Get all available locales where this entity
129
     * does not have any translations of
130
     *
131
     * @return array
132
     */
133
    public function getNonUsedLocales()
134
    {
135
        return $this->fetchLocales(false);
136
    }
137
138
    /**
139
     * Get all locales associated with this entity
140
     *
141
     * @param bool $available
142
     * @return array
143
     */
144
    private function fetchLocales($available = true)
145
    {
146
        $available_locales = static::availableLocales();
147
        $current_locales = $this->translations()->pluck('locale')->toArray();
0 ignored issues
show
Bug introduced by
It seems like translations() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

147
        $current_locales = $this->/** @scrutinizer ignore-call */ translations()->pluck('locale')->toArray();
Loading history...
148
149
        return array_filter($available_locales, function ($v) use ($current_locales, $available) {
150
            return $available ? in_array($v, $current_locales) : !in_array($v, $current_locales);
151
        }, ARRAY_FILTER_USE_BOTH);
152
    }
153
154
    /**
155
     * Is passed locale one of the allowed ones from config?
156
     *
157
     * @param $locale
158
     */
159 4
    private function validateLocale($locale)
160
    {
161 4
        if (!in_array($locale, static::availableLocales())) {
162
            throw new InvalidArgumentException('Locale [' . $locale . '] is not available');
163
        }
164 4
    }
165
166
    /**
167
     * Dimsav translatable trait overrides the toArray in order to
168
     * inject default translations. To ignore this behaviour and
169
     * present the actual values you should use this method.
170
     *
171
     * @return array
172
     */
173
    public function toRawArray()
174
    {
175
        return parent::toArray();
176
    }
177
178
    /**
179
     * Checks how many locales we have configured.
180
     * We use this check to prevent showing of stuff like tabs when we only have 1 locale set up.
181
     *
182
     * @return bool
183
     */
184
    public function hasMultipleApplicationLocales()
185
    {
186
        return count(static::availableLocales()) > 1 ?: false;
187
    }
188
189
    /**
190
     * Set a trans array on this model for use in crud forms.
191
     * This is used for old input.
192
     */
193
    public function injectTranslationForForm()
194
    {
195
        // Make all translations available for our form
196
        $trans = [];
197
        foreach ($this->getUsedLocales() as $locale) {
198
            $trans[$locale] = $this->getTranslation($locale)->toArray();
199
        }
200
        $this->trans = $trans;
0 ignored issues
show
Bug Best Practice introduced by
The property trans does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
201
    }
202
203
    /**
204
     * Retrieve translation value from the injected translations for the form
205
     * Note that this is only valid if the injectTranslationForForm() method
206
     * is called prior to calling this method.
207
     *
208
     * @param $locale
209
     * @param $key
210
     * @return string|null
211
     */
212 1
    public function translateForForm($locale, $key)
213
    {
214 1
        if (!isset($this->trans) || !isset($this->trans[$locale])) {
215 1
            return null;
216
        }
217
218
        return $this->trans[$locale][$key] ?? null;
219
    }
220
221
    /**
222
     * Update or create translatable fields for a translatable entity
223
     *
224
     * @param $translations
225
     * @param TranslatableContract $entity
226
     * @param array $keys pass the columns that need to be translated. these need to match the passed request keys
227
     */
228
    protected function persistTranslations($translations, TranslatableContract $entity, array $keys)
229
    {
230
        foreach ($entity->getAvailableLocales() as $available_locale) {
0 ignored issues
show
Bug introduced by
The method getAvailableLocales() does not exist on Thinktomorrow\Chief\Comm...le\TranslatableContract. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Comm...le\TranslatableContract. ( Ignorable by Annotation )

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

230
        foreach ($entity->/** @scrutinizer ignore-call */ getAvailableLocales() as $available_locale) {
Loading history...
231
            // Remove the product translation if any already exists
232
            // Translation is also removed if all fields of a translation are left empty
233
            if (!isset($translations[$available_locale]) or !($translation = $translations[$available_locale]) or $this->isCompletelyEmpty($keys, $translation)) {
234
                $entity->removeTranslation($available_locale);
235
                continue;
236
            }
237
            $this->persistTranslation($entity, $keys, $translation, $available_locale);
238
        }
239
    }
240
    /**
241
     * Check if certain locale input submission is left empty
242
     *
243
     * @param array $keys
244
     * @param $translation
245
     * @return array
246
     */
247
    protected function isCompletelyEmpty(array $keys, array $translation)
248
    {
249
        $is_completely_empty = true;
250
        foreach ($keys as $key) {
251
            if (!isset($translation[$key])) {
252
                continue;
253
            }
254
            if (trim($translation[$key])) {
255
                $is_completely_empty = false;
256
            }
257
        }
258
        return $is_completely_empty;
259
    }
260
    /**
261
     * @param TranslatableContract $entity
262
     * @param array $keys
263
     * @param $translation
264
     * @param $available_locale
265
     */
266
    protected function persistTranslation(TranslatableContract $entity, array $keys, array $translation, $available_locale)
267
    {
268
        $attributes = [];
269
        foreach ($keys as $key) {
270
            if (isset($translation[$key])) {
271
                $attributes[$key] = $translation[$key];
272
            }
273
        }
274
        $entity->updateTranslation($available_locale, $attributes);
275
    }
276
}
277