1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace WeAreNeopix\LaravelModelTranslation\Drivers; |
4
|
|
|
|
5
|
|
|
use Illuminate\Support\Str; |
6
|
|
|
use Illuminate\Support\Collection; |
7
|
|
|
use Illuminate\Database\Eloquent\Model; |
8
|
|
|
use Illuminate\Filesystem\FilesystemAdapter as StorageDisk; |
9
|
|
|
use WeAreNeopix\LaravelModelTranslation\Contracts\TranslationDriver; |
10
|
|
|
use WeAreNeopix\LaravelModelTranslation\Jobs\SyncModelLanguageMapping; |
11
|
|
|
use WeAreNeopix\LaravelModelTranslation\Jobs\RemoveModelFromLanguageModelMap; |
12
|
|
|
|
13
|
|
|
class JSONTranslationDriver implements TranslationDriver |
14
|
|
|
{ |
15
|
|
|
/** @var StorageDisk */ |
16
|
|
|
protected $disk; |
17
|
|
|
|
18
|
|
|
public function __construct(StorageDisk $disk) |
19
|
|
|
{ |
20
|
|
|
$this->disk = $disk; |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
public function storeTranslationsForModel(Model $model, string $language, array $translations): bool |
24
|
|
|
{ |
25
|
|
|
$pathFromDiskRoot = $this->getJsonPathForModel($model, $language); |
26
|
|
|
|
27
|
|
|
$content = json_encode($translations); |
28
|
|
|
|
29
|
|
|
SyncModelLanguageMapping::dispatch($model, $language); |
30
|
|
|
|
31
|
|
|
return $this->disk->put($pathFromDiskRoot, $content); |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
public function getTranslationsForModel(Model $model, string $language): array |
35
|
|
|
{ |
36
|
|
|
$path = $this->getJsonPathForModel($model, $language); |
37
|
|
|
|
38
|
|
|
if (! $this->disk->exists($path)) { |
39
|
|
|
return []; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
$contents = $this->disk->get($this->getJsonPathForModel($model, $language)); |
43
|
|
|
|
44
|
|
|
return json_decode($contents, true); |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
public function getTranslationsForModels(Collection $models, string $language): Collection |
48
|
|
|
{ |
49
|
|
|
return $models->mapWithKeys(function (Model $model) use ($language) { |
50
|
|
|
$path = $this->getJsonPathForModel($model, $language); |
51
|
|
|
$translationArray = []; |
52
|
|
|
|
53
|
|
|
if ($this->disk->exists($path)) { |
54
|
|
|
$translationsJson = $this->disk->get($this->getJsonPathForModel($model, $language)); |
55
|
|
|
$translationArray = json_decode($translationsJson, true); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
return [ |
59
|
|
|
$model->getInstanceIdentifier() => $translationArray, |
60
|
|
|
]; |
61
|
|
|
}); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
public function getAvailableLanguagesForModel(Model $model): array |
65
|
|
|
{ |
66
|
|
|
$path = $this->getJsonPathForModel($model); |
67
|
|
|
|
68
|
|
|
return array_map(function ($jsonFile) { |
69
|
|
|
return basename($jsonFile, '.json'); |
70
|
|
|
}, $this->disk->files($path)); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
public function getModelsAvailableInLanguage(string $modelIdentifier, string $language): array |
74
|
|
|
{ |
75
|
|
|
$map = $this->getLanguageModelMap($language); |
76
|
|
|
|
77
|
|
|
return $map[$modelIdentifier] ?? []; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
public function putTranslationsForModel(Model $model, string $language, array $translations): bool |
81
|
|
|
{ |
82
|
|
|
return $this->storeTranslationsForModel($model, $language, $translations); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
public function patchTranslationsForModel(Model $model, string $language, array $translations): bool |
86
|
|
|
{ |
87
|
|
|
$existingTranslations = $this->getTranslationsForModel($model, $language); |
88
|
|
|
|
89
|
|
|
$newTranslations = array_merge($existingTranslations, $translations); |
90
|
|
|
|
91
|
|
|
return $this->storeTranslationsForModel($model, $language, $newTranslations); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function deleteAllTranslationsForModel(Model $model): bool |
95
|
|
|
{ |
96
|
|
|
$path = $this->getJsonPathForModel($model); |
97
|
|
|
|
98
|
|
|
RemoveModelFromLanguageModelMap::dispatch($model); |
99
|
|
|
|
100
|
|
|
return $this->disk->deleteDirectory($path); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
public function deleteLanguagesForModel(Model $model, array $languages): bool |
104
|
|
|
{ |
105
|
|
|
$modelDir = $this->getJsonPathForModel($model).DIRECTORY_SEPARATOR; |
106
|
|
|
foreach ($languages as $language) { |
107
|
|
|
$this->disk->delete($modelDir."{$language}.json"); |
108
|
|
|
|
109
|
|
|
SyncModelLanguageMapping::dispatch($model, $language); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
return true; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
public function deleteAttributesForModel(Model $model, array $attributes, string $language = null): bool |
116
|
|
|
{ |
117
|
|
|
$modelDir = $this->getJsonPathForModel($model); |
118
|
|
|
if ($language !== null) { |
119
|
|
|
$paths = [$modelDir.DIRECTORY_SEPARATOR."{$language}.json"]; |
120
|
|
|
if (! $this->disk->exists($paths[0])) { |
121
|
|
|
return true; |
122
|
|
|
} |
123
|
|
|
} else { |
124
|
|
|
$paths = $this->disk->files($modelDir); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
foreach ($paths as $filePath) { |
128
|
|
|
$translations = json_decode($this->disk->get($filePath), true); |
129
|
|
|
$newTranslations = array_diff_key($translations, array_flip($attributes)); |
130
|
|
|
|
131
|
|
|
if (empty($newTranslations)) { |
132
|
|
|
$this->disk->delete($filePath); |
133
|
|
|
} else { |
134
|
|
|
$this->disk->put($filePath, json_encode($newTranslations)); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
$language = pathinfo($filePath)['filename']; |
138
|
|
|
SyncModelLanguageMapping::dispatch($model, $language); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
return true; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
public function syncModelsForLanguage(string $language, Model $model) |
146
|
|
|
{ |
147
|
|
|
if (in_array($language, $this->getAvailableLanguagesForModel($model))) { |
148
|
|
|
$this->addModelToLanguageMap($model, $language); |
149
|
|
|
} else { |
150
|
|
|
$this->removeModelFromLanguageMap($model, $language); |
151
|
|
|
} |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
protected function addModelToLanguageMap(Model $model, string $language) |
155
|
|
|
{ |
156
|
|
|
$modelIdentifier = $model->getModelIdentifier(); |
157
|
|
|
$instanceIdentifier = $model->getInstanceIdentifier(); |
158
|
|
|
$map = $this->getLanguageModelMap($language); |
159
|
|
|
|
160
|
|
|
if (!array_key_exists($modelIdentifier, $map)) { |
161
|
|
|
$map[$modelIdentifier] = [$instanceIdentifier]; |
162
|
|
|
$this->saveMap($map, $language); |
163
|
|
|
} elseif (!in_array($instanceIdentifier, $map[$modelIdentifier])) { |
164
|
|
|
$map[$modelIdentifier][] = $instanceIdentifier; |
165
|
|
|
$this->saveMap($map, $language); |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
protected function removeModelFromLanguageMap(Model $model, string $language) |
170
|
|
|
{ |
171
|
|
|
$modelIdentifier = $model->getModelIdentifier(); |
172
|
|
|
$modelInstanceIdentifier = $model->getInstanceIdentifier(); |
173
|
|
|
$map = $this->getLanguageModelMap($language); |
174
|
|
|
|
175
|
|
|
$instances = collect($map[$modelIdentifier])->filter(function ($instanceIdentifier) use ($modelInstanceIdentifier) { |
176
|
|
|
return $instanceIdentifier != $modelInstanceIdentifier; |
177
|
|
|
})->values(); |
178
|
|
|
|
179
|
|
|
$map[$modelIdentifier] = $instances->toArray(); |
180
|
|
|
|
181
|
|
|
$map = $this->removeRedundancyFromMap($map, $language); |
|
|
|
|
182
|
|
|
|
183
|
|
|
(empty($map)) ? $this->removeMap($language) : $this->saveMap($map, $language); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
public function removeModelFromAllLanguages(Model $model) |
187
|
|
|
{ |
188
|
|
|
$languages = $this->getAvailableLanguagesForModel($model); |
189
|
|
|
foreach ($languages as $language) { |
190
|
|
|
$this->removeModelFromLanguageMap($model, $language); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
|
195
|
|
|
protected function initializeMap(string $language) |
196
|
|
|
{ |
197
|
|
|
$this->disk->put($this->mapName($language), json_encode([])); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
protected function mapName(string $language) |
201
|
|
|
{ |
202
|
|
|
return "meta" . DIRECTORY_SEPARATOR . "{$language}.json"; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
protected function getLanguageModelMap($language) |
206
|
|
|
{ |
207
|
|
|
if (!$this->disk->has($this->mapName($language))) { |
208
|
|
|
$this->initializeMap($language); |
209
|
|
|
return []; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
return json_decode($this->disk->get($this->mapName($language)), true); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
protected function removeRedundancyFromMap(array $map) { |
216
|
|
|
foreach ($map as $modelIdentifier => $instances) { |
217
|
|
|
if (empty($instances)) { |
218
|
|
|
unset($map[$modelIdentifier]); |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
return $map; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
protected function saveMap(array $map, string $language) |
225
|
|
|
{ |
226
|
|
|
$this->disk->put($this->mapName($language), json_encode($map)); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
protected function removeMap(string $language) |
230
|
|
|
{ |
231
|
|
|
$this->disk->delete($this->mapName($language)); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
|
235
|
|
|
protected function getJsonPathForModel(Model $model, string $language = null) |
236
|
|
|
{ |
237
|
|
|
$instanceIdentifier = $model->getInstanceIdentifier(); |
238
|
|
|
$modelIdentifier = $this->normalizeModelIdentifier($model->getModelIdentifier()); |
239
|
|
|
|
240
|
|
|
$path = $modelIdentifier.DIRECTORY_SEPARATOR.$instanceIdentifier; |
241
|
|
|
if ($language !== null) { |
242
|
|
|
$path .= DIRECTORY_SEPARATOR."{$language}.json"; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
return $path; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
protected function normalizeModelIdentifier($modelIdentifier) |
249
|
|
|
{ |
250
|
|
|
$modelIdentifier = str_replace('\\', '_', $modelIdentifier); |
251
|
|
|
|
252
|
|
|
return Str::slug($modelIdentifier); |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.