TranslationRepository::updateDefaultByCode()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Translation\Repositories;
4
5
use Illuminate\Database\Query\JoinClause;
6
use Illuminate\Foundation\Application;
0 ignored issues
show
Bug introduced by
The type Illuminate\Foundation\Application was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Illuminate\Support\NamespacedItemResolver;
8
use Translation\Models\Translation as Translation;
9
use Illuminate\Support\Facades\Schema;
10
11
class TranslationRepository extends Repository
12
{
13
    /**
14
     * @var \Illuminate\Database\Connection
15
     */
16
    protected $database;
17
18
    /**
19
     * The model being queried.
20
     *
21
     * @var \Translation\Models\Translation
22
     */
23
    protected $model;
24
25
    /**
26
     *  Validator
27
     *
28
     * @var \Illuminate\Validation\Validator
29
     */
30
    protected $validator;
31
32
    /**
33
     *  Validation errors.
34
     *
35
     * @var \Illuminate\Support\MessageBag
36
     */
37
    protected $errors;
38
39
    /**
40
     *  Constructor
41
     *
42
     * @param  \Translation\Models\Translation  $model     Bade model for queries.
43
     * @param  \Illuminate\Validation\Validator $validator Validator factory
44
     * @return void
45
     */
46
    public function __construct(Translation $model, Application $app)
47
    {
48
        $this->model         = $model;
49
        $this->app           = $app;
0 ignored issues
show
Bug Best Practice introduced by
The property app does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
50
        $this->defaultLocale = $app['config']->get('app.locale');
0 ignored issues
show
Bug Best Practice introduced by
The property defaultLocale does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
51
        $this->database      = $app['db'];
52
    }
53
54
    /**
55
     *  Insert a new translation into the database.
56
     *  If the attributes are not valid, a null response is given and the errors can be retrieved through validationErrors()
57
     *
58
     * @param  array $attributes Model attributes
59
     * @return boolean
60
     */
61
    public function create(array $attributes)
62
    {
63
        return $this->validate($attributes) ? Translation::create($attributes) : null;
64
    }
65
66
    /**
67
     *  Update a translation.
68
     *  If the translation is locked, no update will be made.
69
     *
70
     * @param  array $attributes Model attributes
71
     * @return boolean
72
     */
73
    public function update($id, string $text)
74
    {
75
        $translation = $this->find($id);
76
        if (!$translation || $translation->isLocked()) {
0 ignored issues
show
introduced by
$translation is of type Translation\Models\Translation, thus it always evaluated to true.
Loading history...
77
            return false;
78
        }
79
        $translation->text = $text;
80
        $saved             = $translation->save();
81
        if ($saved && $translation->locale === $this->defaultLocale) {
82
            $this->flagAsUnstable($translation->namespace, $translation->group, $translation->item);
83
        }
84
        return $saved;
85
    }
86
87
    /**
88
     *  Update and lock translation. Locked translations will not be ovewritten when loading translation files into the database.
89
     *  This will force and update if the translation is locked.
90
     *  If the attributes are not valid, a null response is given and the errors can be retrieved through validationErrors()
91
     *
92
     * @param  array $attributes Model attributes
93
     * @return boolean
94
     */
95
    public function updateAndLock($id, $text)
96
    {
97
        $translation = $this->find($id);
98
        if (!$translation) {
0 ignored issues
show
introduced by
$translation is of type Translation\Models\Translation, thus it always evaluated to true.
Loading history...
99
            return false;
100
        }
101
        $translation->text = $text;
102
        $translation->lock();
103
        $saved = $translation->save();
104
        if ($saved && $translation->locale === $this->defaultLocale) {
105
            $this->flagAsUnstable($translation->namespace, $translation->group, $translation->item);
106
        }
107
        return $saved;
108
    }
109
110
    /**
111
     *  Insert or Update entry by translation code for the default locale.
112
     *
113
     * @param  string $code
114
     * @param  string $text
115
     * @return boolean
116
     */
117
    public function updateDefaultByCode($code, $text)
118
    {
119
        list($namespace, $group, $item) = $this->parseCode($code);
120
        $locale                         = $this->defaultLocale;
121
        $translation                    = $this->model->whereLocale($locale)->whereNamespace($namespace)->whereGroup($group)->whereItem($item)->first();
122
        if (!$translation) {
123
            return $this->create(compact('locale', 'namespace', 'group', 'item', 'text'));
124
        }
125
        return $this->update($translation->id, $text);
126
    }
127
128
    /**
129
     *  Delete a translation. If the translation is of the default language, delete all translations with the same namespace, group and item
130
     *
131
     * @param  integer $id
132
     * @return boolean
133
     */
134
    public function delete($id)
135
    {
136
        $translation = $this->find($id);
137
        if (!$translation) {
0 ignored issues
show
introduced by
$translation is of type Translation\Models\Translation, thus it always evaluated to true.
Loading history...
138
            return false;
139
        }
140
141
        if ($translation->locale === $this->defaultLocale) {
142
            return $this->model->whereNamespace($translation->namespace)->whereGroup($translation->group)->whereItem($translation->item)->delete();
143
        } else {
144
            return $translation->delete();
145
        }
146
    }
147
148
    /**
149
     *  Delete all entries by code
150
     *
151
     * @param  string $code
152
     * @return boolean
153
     */
154
    public function deleteByCode($code)
155
    {
156
        list($namespace, $group, $item) = $this->parseCode($code);
157
        $this->model->whereNamespace($namespace)->whereGroup($group)->whereItem($item)->delete();
158
    }
159
160
    /**
161
     *  Loads a localization array from a localization file into the databas.
162
     *
163
     * @param  array  $lines
164
     * @param  string $locale
165
     * @param  string $group
166
     * @param  string $namespace
167
     * @return void
168
     */
169
    public function loadArray(array $lines, $locale, $group, $namespace = '*')
170
    {
171
        // Transform the lines into a flat dot array:
172
        $lines = array_dot($lines);
173
        foreach ($lines as $item => $text) {
174
            if (is_string($text)) {
175
                // Check if the entry exists in the database:
176
                $translation = Translation::whereLocale($locale)
177
                    ->whereNamespace($namespace)
178
                    ->whereGroup($group)
179
                    ->whereItem($item)
180
                    ->first();
181
182
                // If the translation already exists, we update the text:
183
                if ($translation && !$translation->isLocked()) {
184
                    $translation->text = $text;
185
                    $saved             = $translation->save();
186
                    if ($saved && $translation->locale === $this->defaultLocale) {
187
                        $this->flagAsUnstable($namespace, $group, $item);
188
                    }
189
                }
190
                // If no entry was found, create it:
191
                else {
192
                    $this->create(compact('locale', 'namespace', 'group', 'item', 'text'));
193
                }
194
            }
195
        }
196
    }
197
198
    /**
199
     *  Return a list of translations for the given language. If perPage is > 0 a paginated list is returned with perPage items per page.
200
     *
201
     * @param  string $locale
202
     * @return Translation
203
     */
204
    public function allByLocale($locale, $perPage = 0)
205
    {
206
        $translations = $this->model->where('locale', $locale);
207
        return $perPage ? $translations->paginate($perPage) : $translations->get();
208
    }
209
210
    /**
211
     *  Return all items for a given locale, namespace and group
212
     *
213
     * @param  string $locale
214
     * @param  string $namespace
215
     * @param  string $group
216
     * @return array
217
     */
218
    public function getItems($locale, $namespace, $group)
219
    {
220
        return $this->model
221
            ->whereLocale($locale)
222
            ->whereNamespace($namespace)
223
            ->whereGroup($group)
224
            ->get()
225
            ->toArray();
226
    }
227
228
    /**
229
     *  Return all items formatted as if coming from a PHP language file.
230
     *
231
     * @param  string $locale
232
     * @param  string $namespace
233
     * @param  string $group
234
     * @return array
235
     */
236
    public function loadSource($locale, $namespace, $group)
237
    {
238
239
        if (!Schema::hasTable('translations')) {
240
            return [];
241
        }
242
        return $this->model
243
            ->whereLocale($locale)
244
            ->whereNamespace($namespace)
245
            ->whereGroup($group)
246
            ->get()
247
            ->keyBy('item')
248
            ->map(
249
                function ($translation) {
250
                    return $translation['text'];
251
                }
252
            )
253
            ->toArray();
254
    }
255
256
    /**
257
     *  Retrieve translations pending review for the given locale.
258
     *
259
     * @param  string $locale
260
     * @param  int    $perPage Number of elements per page. 0 if all are wanted.
261
     * @return Translation
262
     */
263
    public function pendingReview($locale, $perPage = 0)
264
    {
265
        $underReview = $this->model->whereLocale($locale)->whereUnstable(1);
266
        return $perPage ? $underReview->paginate($perPage) : $underReview->get();
267
    }
268
269
    /**
270
     *  Search for entries given a partial code and a locale
271
     *
272
     * @param  string  $locale
273
     * @param  string  $partialCode
274
     * @param  integer $perPage     0 if all, > 0 if paginated list with that number of elements per page.
275
     * @return Translation
276
     */
277
    public function search($locale, $partialCode, $perPage = 0)
278
    {
279
        // Get the namespace, if any:
280
        $colonIndex = stripos($partialCode, '::');
281
        $query      = $this->model->whereLocale($locale);
282
        if ($colonIndex === 0) {
283
            $query = $query->where('namespace', '!=', '*');
284
        } elseif ($colonIndex > 0) {
285
            $namespace   = substr($partialCode, 0, $colonIndex);
286
            $query       = $query->where('namespace', 'like', "%{$namespace}%");
287
            $partialCode = substr($partialCode, $colonIndex + 2);
288
        }
289
290
        // Divide the code in segments by .
291
        $elements = explode('.', $partialCode);
292
        foreach ($elements as $element) {
293
            if ($element) {
294
                $query = $query->where(
295
                    function ($query) use ($element) {
296
                        $query->where('group', 'like', "%{$element}%")->orWhere('item', 'like', "%{$element}%")->orWhere('text', 'like', "%{$element}%");
297
                    }
298
                );
299
            }
300
        }
301
302
        return $perPage ? $query->paginate($perPage) : $query->get();
303
    }
304
305
    /**
306
     *  List all entries in the default locale that do not exist for the target locale.
307
     *
308
     * @param  string  $locale  Language to translate to.
309
     * @param  integer $perPage If greater than zero, return a paginated list with $perPage items per page.
310
     * @param  string  $text    [optional] Show only entries with the given text in them in the reference language.
311
     * @return Collection
0 ignored issues
show
Bug introduced by
The type Translation\Repositories\Collection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
312
     */
313
    public function untranslated($locale, $perPage = 0, $text = null)
314
    {
315
        $ids = $this->untranslatedQuery($locale)->pluck('id');
316
317
        $untranslated = $text ? $this->model->whereIn('id', $ids)->where('text', 'like', "%$text%") : $this->model->whereIn('id', $ids);
318
319
        return $perPage ? $untranslated->paginate($perPage) : $untranslated->get();
320
    }
321
322
    /**
323
     *  Find a random entry that is present in the default locale but not in the given one.
324
     *
325
     * @param  string $locale Locale to translate to.
326
     * @return Translation
327
     */
328
    public function randomUntranslated($locale)
329
    {
330
        return $this->untranslatedQuery($locale)->inRandomOrder()->take(1)->pluck('id');
331
    }
332
333
    /**
334
     *  Find a translation per namespace, group and item values
335
     *
336
     * @param  string $locale
337
     * @param  string $namespace
338
     * @param  string $group
339
     * @param  string $item
340
     * @return Translation
341
     */
342
    public function findByLangCode($locale, $code)
343
    {
344
        list($namespace, $group, $item) = $this->parseCode($code);
345
        return $this->model->whereLocale($locale)->whereNamespace($namespace)->whereGroup($group)->whereItem($item)->first();
346
    }
347
348
    /**
349
     *  Find a translation per namespace, group and item values
350
     *
351
     * @param  string $locale
352
     * @param  string $namespace
353
     * @param  string $group
354
     * @param  string $item
355
     * @return Translation
356
     */
357
    public function findByCode($locale, $namespace, $group, $item)
358
    {
359
        return $this->model->whereLocale($locale)->whereNamespace($namespace)->whereGroup($group)->whereItem($item)->first();
360
    }
361
362
    /**
363
     *  Check if there are existing translations for the given text in the given locale for the target locale.
364
     *
365
     * @param  string $text
366
     * @param  string $textLocale
367
     * @param  string $targetLocale
368
     * @return array
369
     */
370
    public function translateText($text, $textLocale, $targetLocale)
371
    {
372
        $table = $this->model->getTable();
373
374
        return $this->model
375
            ->newQuery()
376
            ->select($table . '.text')
377
            ->from($table)
378
            ->leftJoin(
379
                "{$table} as e", function ($join) use ($table, $text, $textLocale) {
0 ignored issues
show
Unused Code introduced by
The import $text is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
Unused Code introduced by
The import $textLocale is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
380
                    $join->on('e.namespace', '=', "{$table}.namespace")
381
                        ->on('e.group', '=', "{$table}.group")
382
                        ->on('e.item', '=', "{$table}.item");
383
                }
384
            )
385
            ->where("{$table}.locale", $targetLocale)
386
            ->where('e.locale', $textLocale)
387
            ->where('e.text', $text)
388
            ->get()
389
            ->pluck('text')
390
            ->unique()
391
            ->toArray();
392
    }
393
394
    /**
395
     *  Flag all entries with the given namespace, group and item and locale other than default as pending review.
396
     *  This is used when an entry for the default locale is updated.
397
     *
398
     * @param  Translation $entry
399
     * @return boolean
400
     */
401
    public function flagAsUnstable(string $namespace, string $group, $item)
402
    {
403
        $this->model->whereNamespace($namespace)->whereGroup($group)->whereItem($item)->where('locale', '!=', $this->defaultLocale)->update(['unstable' => '1']);
404
    }
405
406
    /**
407
     *  Flag the entry with the given id as reviewed.
408
     *
409
     * @param  integer $id
410
     * @return boolean
411
     */
412
    public function flagAsReviewed($id)
413
    {
414
        $this->model->where('id', $id)->update(['unstable' => '0']);
415
    }
416
417
    /**
418
     *  Validate the given attributes
419
     *
420
     * @param  array $attributes
421
     * @return boolean
422
     */
423
    public function validate(array $attributes)
424
    {
425
        $table     = $this->model->getTable();
426
        $locale    = array_get($attributes, 'locale', '');
427
        $namespace = array_get($attributes, 'namespace', '');
428
        $group     = array_get($attributes, 'group', '');
429
        $rules     = [
430
            'locale'    => 'required',
431
            'namespace' => 'required',
432
            'group'     => 'required',
433
            'item'      => "required|unique:{$table},item,NULL,id,locale,{$locale},namespace,{$namespace},group,{$group}",
434
            'text'      => '', // Translations may be empty
435
        ];
436
        $validator = $this->app['validator']->make($attributes, $rules);
437
        if ($validator->fails()) {
438
            $this->errors = $validator->errors();
439
            return false;
440
        }
441
        return true;
442
    }
443
444
    /**
445
     *  Returns the validations errors of the last action executed.
446
     *
447
     * @return \Illuminate\Support\MessageBag
448
     */
449
    public function validationErrors()
450
    {
451
        return $this->errors;
452
    }
453
454
    /**
455
     *  Parse a translation code into its components
456
     *
457
     * @param  string $code
458
     * @return boolean
459
     */
460
    public function parseCode($code)
461
    {
462
        $segments = (new NamespacedItemResolver)->parseKey($code);
463
464
        if (is_null($segments[0])) {
465
            $segments[0] = '*';
466
        }
467
468
        return $segments;
469
    }
470
471
    /**
472
     * Create and return a new query to identify untranslated records.
473
     *
474
     * @param  string $locale
475
     * @return \Illuminate\Database\Query\Builder
476
     */
477
    protected function untranslatedQuery($locale)
478
    {
479
        $table = $this->model->getTable();
480
481
        return $this->database->table("$table as $table")
482
            ->select("$table.id")
483
            ->leftJoin(
484
                "$table as e", function (JoinClause $query) use ($table, $locale) {
485
                    $query->on('e.namespace', '=', "$table.namespace")
486
                        ->on('e.group', '=', "$table.group")
487
                        ->on('e.item', '=', "$table.item")
488
                        ->where('e.locale', '=', $locale);
489
                }
490
            )
491
            ->where("$table.locale", $this->defaultLocale)
492
            ->whereNull("e.id");
493
    }
494
495
}
496