This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Happyr\TranslationBundle\Service; |
||
4 | |||
5 | use Happyr\TranslationBundle\Exception\HappyrTranslationException; |
||
6 | use Happyr\TranslationBundle\Exception\HttpException; |
||
7 | use Happyr\TranslationBundle\Http\RequestManager; |
||
8 | use Happyr\TranslationBundle\Model\Message; |
||
9 | use Happyr\TranslationBundle\Translation\FilesystemUpdater; |
||
10 | use Symfony\Component\Filesystem\Exception\FileNotFoundException; |
||
11 | use Symfony\Component\Translation\TranslatorInterface; |
||
12 | |||
13 | /** |
||
14 | * @author Tobias Nyholm <[email protected]> |
||
15 | */ |
||
16 | class Loco implements TranslationServiceInterface |
||
17 | { |
||
18 | const BASE_URL = 'https://localise.biz/api/'; |
||
19 | |||
20 | /** |
||
21 | * @var RequestManager |
||
22 | */ |
||
23 | private $requestManager; |
||
24 | |||
25 | /** |
||
26 | * @var array projects |
||
27 | */ |
||
28 | private $projects; |
||
29 | |||
30 | /** |
||
31 | * @var FilesystemUpdater filesystemService |
||
32 | */ |
||
33 | private $filesystemService; |
||
34 | |||
35 | /** |
||
36 | * @var TranslatorInterface filesystemService |
||
37 | */ |
||
38 | private $translator; |
||
39 | |||
40 | /** |
||
41 | * @param TranslatorInterface $translator |
||
42 | * @param RequestManager $requestManager |
||
43 | * @param FilesystemUpdater $fs |
||
44 | * @param array $projects |
||
45 | */ |
||
46 | 1 | public function __construct(RequestManager $requestManager, FilesystemUpdater $fs, TranslatorInterface $translator, array $projects) |
|
47 | { |
||
48 | 1 | $this->translator = $translator; |
|
49 | 1 | $this->requestManager = $requestManager; |
|
50 | 1 | $this->projects = $projects; |
|
51 | 1 | $this->filesystemService = $fs; |
|
52 | 1 | } |
|
53 | |||
54 | /** |
||
55 | * @param $key |
||
56 | * @param $method |
||
57 | * @param $resource |
||
58 | * @param null $body |
||
59 | * @param string $type |
||
60 | * @param array $extraQuery |
||
61 | * @return array |
||
62 | * @throws HttpException |
||
63 | */ |
||
64 | protected function makeApiRequest($key, $method, $resource, $body = null, $type = 'form', $extraQuery = array()) |
||
65 | { |
||
66 | $headers = array(); |
||
67 | if ($body !== null) { |
||
68 | if ($type === 'form') { |
||
69 | if (is_array($body)) { |
||
70 | $body = http_build_query($body); |
||
71 | } |
||
72 | $headers['Content-Type'] = 'application/x-www-form-urlencoded'; |
||
73 | } elseif ($type === 'json') { |
||
74 | $body = json_encode($body); |
||
75 | $headers['Content-Type'] = 'application/json'; |
||
76 | } |
||
77 | } |
||
78 | |||
79 | $query = array_merge($extraQuery, ['key' => $key]); |
||
80 | $url = self::BASE_URL . $resource . '?' . http_build_query($query); |
||
81 | |||
82 | return $this->requestManager->send($method, $url, $body, $headers); |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * Fetch a translation form Loco. |
||
87 | * |
||
88 | * @param Message $message |
||
89 | */ |
||
90 | public function fetchTranslation(Message $message, $updateFs = false) |
||
91 | { |
||
92 | $project = $this->getProject($message); |
||
93 | |||
94 | try { |
||
95 | $resource = sprintf('translations/%s/%s', $message->getId(), $message->getLocale()); |
||
96 | $response = $this->makeApiRequest($project['api_key'], 'GET', $resource); |
||
97 | } catch (HttpException $e) { |
||
98 | if ($e->getCode() === 404) { |
||
99 | //Message does not exist |
||
100 | return; |
||
101 | } |
||
102 | throw $e; |
||
103 | } |
||
104 | |||
105 | $logoTranslation = $response['translation']; |
||
106 | $messageTranslation = $message->getTranslation(); |
||
107 | $message->setTranslation($logoTranslation); |
||
108 | |||
109 | // update filesystem |
||
110 | if ($updateFs && $logoTranslation !== $messageTranslation) { |
||
111 | $this->filesystemService->updateMessageCatalog([$message]); |
||
112 | } |
||
113 | |||
114 | return $logoTranslation; |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Update the translation in Loco. |
||
119 | * |
||
120 | * @param Message $message |
||
121 | */ |
||
122 | public function updateTranslation(Message $message) |
||
123 | { |
||
124 | $project = $this->getProject($message); |
||
125 | |||
126 | try { |
||
127 | $resource = sprintf('translations/%s/%s', $message->getId(), $message->getLocale()); |
||
128 | $this->makeApiRequest($project['api_key'], 'POST', $resource, $message->getTranslation()); |
||
129 | } catch (HttpException $e) { |
||
130 | if ($e->getCode() === 404) { |
||
131 | //Asset does not exist |
||
132 | if ($this->createAsset($message)) { |
||
133 | //Try again |
||
134 | return $this->updateTranslation($message); |
||
135 | } |
||
136 | |||
137 | return false; |
||
138 | } |
||
139 | throw $e; |
||
140 | } |
||
141 | |||
142 | $this->filesystemService->updateMessageCatalog([$message]); |
||
143 | |||
144 | return true; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * If there is something wrong with the translation, please flag it. |
||
149 | * |
||
150 | * @param Message $message |
||
151 | * @param int $type 0: Fuzzy, 1: Incorrect, 2: Provisional, 3: Unapproved, 4: Incomplete |
||
152 | * |
||
153 | * @return bool |
||
154 | */ |
||
155 | public function flagTranslation(Message $message, $type = 0) |
||
156 | { |
||
157 | $project = $this->getProject($message); |
||
158 | $flags = ['fuzzy', 'incorrect', 'provisional', 'unapproved', 'incomplete']; |
||
159 | |||
160 | try { |
||
161 | $resource = sprintf('translations/%s/%s/flag', $message->getId(), $message->getLocale()); |
||
162 | $this->makeApiRequest($project['api_key'], 'POST', $resource, ['flag' => $flags[$type]]); |
||
163 | } catch (HttpException $e) { |
||
164 | if ($e->getCode() === 404) { |
||
165 | //Message does not exist |
||
166 | return false; |
||
167 | } |
||
168 | throw $e; |
||
169 | } |
||
170 | |||
171 | return true; |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * Create a new asset in Loco. |
||
176 | * |
||
177 | * @param Message $message |
||
178 | * |
||
179 | * @return bool |
||
180 | */ |
||
181 | public function createAsset(Message $message) |
||
182 | { |
||
183 | $project = $this->getProject($message); |
||
184 | |||
185 | try { |
||
186 | $response = $this->makeApiRequest($project['api_key'], 'POST', 'assets', [ |
||
187 | 'id' => $message->getId(), |
||
188 | 'name' => $message->getId(), |
||
189 | 'type' => 'text', |
||
190 | // Tell Loco not to translate the asset |
||
191 | 'default' => 'untranslated', |
||
192 | ]); |
||
193 | |||
194 | if ($message->hasParameters()) { |
||
195 | // Send those parameter as a note to Loco |
||
196 | $notes = ''; |
||
197 | foreach ($message->getParameters() as $key => $value) { |
||
198 | if (!is_array($value)) { |
||
199 | $notes .= 'Parameter: ' . $key . ' (i.e. : ' . $value . ")\n"; |
||
200 | } else { |
||
201 | foreach ($value as $k => $v) { |
||
202 | $notes .= 'Parameter: ' . $k . ' (i.e. : ' . $v . ")\n"; |
||
203 | } |
||
204 | } |
||
205 | } |
||
206 | |||
207 | $resource = sprintf('assets/%s.json', $message->getId()); |
||
208 | $this->makeApiRequest($project['api_key'], 'PATCH', $resource, ['notes' => $notes], 'json'); |
||
209 | } |
||
210 | } catch (HttpException $e) { |
||
211 | if ($e->getCode() === 409) { |
||
212 | //conflict.. ignore |
||
213 | return false; |
||
214 | } |
||
215 | throw $e; |
||
216 | } |
||
217 | |||
218 | // if this project has multiple domains. Make sure to tag it |
||
219 | if (!empty($project['domains'])) { |
||
220 | $this->addTagToAsset($project, $response['id'], $message->getDomain()); |
||
221 | } |
||
222 | |||
223 | return true; |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * @param Message $message |
||
228 | * |
||
229 | * @return array |
||
230 | */ |
||
231 | protected function getProject(Message $message) |
||
232 | { |
||
233 | if (isset($this->projects[$message->getDomain()])) { |
||
234 | return $this->projects[$message->getDomain()]; |
||
235 | } |
||
236 | |||
237 | // Return the first project that has the correct domain and locale |
||
238 | foreach ($this->projects as $project) { |
||
239 | if (in_array($message->getDomain(), $project['domains'])) { |
||
240 | if (in_array($message->getLocale(), $project['locales'])) { |
||
241 | return $project; |
||
242 | } |
||
243 | } |
||
244 | } |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * @param $project |
||
249 | * @param $messageId |
||
250 | * @param $domain |
||
251 | */ |
||
252 | protected function addTagToAsset($project, $messageId, $domain) |
||
253 | { |
||
254 | $resource = sprintf('assets/%s/tags', $messageId); |
||
255 | $this->makeApiRequest($project['api_key'], 'POST', $resource, ['name' => $domain]); |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * Download all the translations from Loco. This will replace all the local files. |
||
260 | * This is a quick method of getting all the latest translations and assets. |
||
261 | */ |
||
262 | public function downloadAllTranslations() |
||
263 | { |
||
264 | $data = []; |
||
265 | foreach ($this->projects as $name => $config) { |
||
266 | if (empty($config['domains'])) { |
||
267 | $this->getUrls($data, $config, $name, false); |
||
268 | } else { |
||
269 | foreach ($config['domains'] as $domain) { |
||
270 | $this->getUrls($data, $config, $domain, true); |
||
271 | } |
||
272 | } |
||
273 | } |
||
274 | $this->requestManager->downloadFiles($this->filesystemService, $data); |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Upload all the translations from the symfony project into Loco. This will override |
||
279 | * every changed strings in loco |
||
280 | */ |
||
281 | View Code Duplication | public function uploadAllTranslations() |
|
0 ignored issues
–
show
|
|||
282 | { |
||
283 | foreach ($this->projects as $name => $config) { |
||
284 | if (empty($config['domains'])) { |
||
285 | $this->doUploadDomains($config, $name, false); |
||
286 | } else { |
||
287 | foreach ($config['domains'] as $domain) { |
||
288 | $this->doUploadDomains($config, $domain, true); |
||
289 | } |
||
290 | } |
||
291 | } |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * @param array $config |
||
296 | * @param $domain |
||
297 | * @param $useDomainAsFilter |
||
298 | */ |
||
299 | protected function doUploadDomains(array &$config, $domain, $useDomainAsFilter) |
||
300 | { |
||
301 | $query = $this->getExportQueryParams($config['api_key']); |
||
302 | |||
303 | if ($useDomainAsFilter) { |
||
304 | $query['filter'] = $domain; |
||
305 | } |
||
306 | |||
307 | foreach ($config['locales'] as $locale) { |
||
308 | $extension = $this->filesystemService->getFileExtension(); |
||
309 | $file = $this->filesystemService->getTargetDir(); |
||
310 | $file .= sprintf('/%s.%s.%s', $domain, $locale, $extension); |
||
311 | |||
312 | if (is_file($file)) { |
||
313 | $query = [ |
||
314 | 'index' => 'id', |
||
315 | 'tag' => $domain, |
||
316 | 'locale'=> $locale |
||
317 | ]; |
||
318 | |||
319 | $resource = sprintf('import/%s', $extension); |
||
320 | $response = $this->makeApiRequest($config['api_key'], 'POST', $resource, file_get_contents($file), 'form', $query); |
||
321 | $this->flatten($response); |
||
322 | } else { |
||
323 | throw new FileNotFoundException(sprintf("Can't find %s file, perhaps you should generate the translations file ?", $file)); |
||
324 | } |
||
325 | } |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Synchronize all the translations with Loco. This will keep placeholders. This function is slower |
||
330 | * than just to download the translations. |
||
331 | */ |
||
332 | View Code Duplication | public function synchronizeAllTranslations() |
|
0 ignored issues
–
show
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. ![]() |
|||
333 | { |
||
334 | foreach ($this->projects as $name => $config) { |
||
335 | if (empty($config['domains'])) { |
||
336 | $this->doSynchronizeDomain($config, $name, false); |
||
337 | } else { |
||
338 | foreach ($config['domains'] as $domain) { |
||
339 | $this->doSynchronizeDomain($config, $domain, true); |
||
340 | } |
||
341 | } |
||
342 | } |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * @param array $config |
||
347 | * @param $domain |
||
348 | * @param $useDomainAsFilter |
||
349 | */ |
||
350 | protected function doSynchronizeDomain(array &$config, $domain, $useDomainAsFilter) |
||
351 | { |
||
352 | $query = $this->getExportQueryParams($config['api_key']); |
||
353 | |||
354 | if ($useDomainAsFilter) { |
||
355 | $query['filter'] = $domain; |
||
356 | } |
||
357 | |||
358 | foreach ($config['locales'] as $locale) { |
||
359 | $resource = sprintf('export/locale/%s.%s', $locale, 'json'); |
||
360 | $response = $this->makeApiRequest($config['api_key'], 'GET', $resource, ['query' => $query]); |
||
361 | |||
362 | $this->flatten($response); |
||
363 | |||
364 | $messages = array(); |
||
365 | foreach ($response as $id => $translation) { |
||
366 | $messages[] = new Message([ |
||
367 | 'count' => 1, |
||
368 | 'domain' => $domain, |
||
369 | 'id' => $id, |
||
370 | 'locale' => $locale, |
||
371 | 'state' => 1, |
||
372 | 'translation' => $translation, |
||
373 | ]); |
||
374 | } |
||
375 | |||
376 | $this->filesystemService->updateMessageCatalog($messages); |
||
377 | } |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * Flattens an nested array of translations. |
||
382 | * |
||
383 | * The scheme used is: |
||
384 | * 'key' => array('key2' => array('key3' => 'value')) |
||
385 | * Becomes: |
||
386 | * 'key.key2.key3' => 'value' |
||
387 | * |
||
388 | * This function takes an array by reference and will modify it |
||
389 | * |
||
390 | * @param array &$messages The array that will be flattened |
||
391 | * @param array $subnode Current subnode being parsed, used internally for recursive calls |
||
392 | * @param string $path Current path being parsed, used internally for recursive calls |
||
393 | */ |
||
394 | private function flatten(array &$messages, array $subnode = null, $path = null) |
||
395 | { |
||
396 | if (null === $subnode) { |
||
397 | $subnode = &$messages; |
||
398 | } |
||
399 | foreach ($subnode as $key => $value) { |
||
400 | if (is_array($value)) { |
||
401 | $nodePath = $path ? $path . '.' . $key : $key; |
||
402 | $this->flatten($messages, $value, $nodePath); |
||
403 | if (null === $path) { |
||
404 | unset($messages[$key]); |
||
405 | } |
||
406 | } elseif (null !== $path) { |
||
407 | $messages[$path . '.' . $key] = $value; |
||
408 | } |
||
409 | } |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * @param array $data |
||
414 | * @param array $config |
||
415 | * @param string $domain |
||
416 | * @param bool $useDomainAsFilter |
||
417 | */ |
||
418 | protected function getUrls(array &$data, array $config, $domain, $useDomainAsFilter) |
||
419 | { |
||
420 | $query = $this->getExportQueryParams($config['api_key']); |
||
421 | |||
422 | if ($useDomainAsFilter) { |
||
423 | $query['filter'] = $domain; |
||
424 | } |
||
425 | |||
426 | foreach ($config['locales'] as $locale) { |
||
427 | // Build url |
||
428 | $url = sprintf('%sexport/locale/%s.%s?%s', self::BASE_URL, $locale, $this->filesystemService->getFileExtension(), http_build_query($query)); |
||
429 | $fileName = sprintf('%s.%s.%s', $domain, $locale, $this->filesystemService->getFileExtension()); |
||
430 | |||
431 | $data[$url] = $fileName; |
||
432 | } |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * @param array $config |
||
0 ignored issues
–
show
There is no parameter named
$config . Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not. ![]() |
|||
437 | * |
||
438 | * @return array |
||
439 | */ |
||
440 | private function getExportQueryParams($key) |
||
441 | { |
||
442 | $data = array( |
||
443 | 'index' => 'id', |
||
444 | 'status' => 'translated', |
||
445 | 'key' => $key, |
||
446 | ); |
||
447 | switch ($this->filesystemService->getFileExtension()) { |
||
448 | case 'php': |
||
449 | $data['format'] = 'zend'; // 'Zend' will give us a flat array |
||
450 | break; |
||
451 | case 'xlf': |
||
452 | default: |
||
453 | $data['format'] = 'symfony'; |
||
454 | } |
||
455 | |||
456 | return $data; |
||
457 | } |
||
458 | } |
||
459 |
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.