Translator::copyFileTranslator()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 0
1
<?php
2
3
/**
4
 * @package Translator
5
 * @author Iurii Makukh <[email protected]>
6
 * @copyright Copyright (c) 2017, Iurii Makukh <[email protected]>
7
 * @license https://www.gnu.org/licenses/gpl-3.0.en.html GPL-3.0+
8
 */
9
10
namespace gplcart\modules\translator\controllers;
11
12
use gplcart\core\controllers\backend\Controller;
13
use gplcart\core\models\FileTransfer;
14
use gplcart\modules\translator\models\Translator as TranslatorModel;
15
16
/**
17
 * Handles incoming requests and outputs data related to Translator module
18
 */
19
class Translator extends Controller
20
{
21
    /**
22
     * Translator model class instance
23
     * @var \gplcart\modules\translator\models\Translator $translator
24
     */
25
    protected $translator;
26
27
    /**
28
     * File transfer model class instance
29
     * @var \gplcart\core\models\FileTransfer $file_transfer
30
     */
31
    protected $file_transfer;
32
33
    /**
34
     * An array of translation strings
35
     * @var array
36
     */
37
    protected $data_content;
38
39
    /**
40
     * The current language
41
     * @var array
42
     */
43
    protected $data_language;
44
45
    /**
46
     * Translator constructor.
47
     * @param FileTransfer $file_transfer
48
     * @param TranslatorModel $translator
49
     */
50
    public function __construct(FileTransfer $file_transfer, TranslatorModel $translator)
51
    {
52
        parent::__construct();
53
54
        $this->translator = $translator;
55
        $this->file_transfer = $file_transfer;
56
    }
57
58
    /**
59
     * Page callback
60
     * Displays the compiled files overview page
61
     * @param $langcode
62
     */
63
    public function compiledFilesTranslator($langcode)
64
    {
65
        $this->filesTranslator($langcode, 'compiled');
66
    }
67
68
    /**
69
     * Page callback
70
     * Displays the file overview page
71
     * @param string $langcode
72
     * @param string $tab
73
     */
74
    public function filesTranslator($langcode = null, $tab = '')
75
    {
76
        $this->setLanguageTranslator($langcode);
77
        $this->downloadFileTranslator();
78
        $this->actionFilesTranslator();
79
        $this->setTitleFilesTranslator();
80
        $this->setBreadcrumbFilesTranslator();
81
82
        $this->setData('tab', $tab);
83
        $this->setData('language', $this->data_language);
84
        $this->setData('files', $this->getFilesTranslator($tab));
85
        $this->setData('languages', $this->getLanguagesTranslator());
86
87
        $this->outputFilesTranslator();
88
    }
89
90
    /**
91
     * Returns a sorted array of languages
92
     * @return array
93
     */
94
    protected function getLanguagesTranslator()
95
    {
96
        $list = $this->language->getList();
97
        gplcart_array_sort($list, 'name');
98
        return $list;
99
    }
100
101
    /**
102
     * Download a translation file
103
     */
104
    protected function downloadFileTranslator()
105
    {
106
        $hash = $this->getQuery('download');
107
108
        if (!empty($hash) && $this->access('module_translator_download')) {
109
110
            $info = $this->parseFileHash($hash);
111
112
            if (!empty($info)) {
113
                $this->download($info[0], $info[1], array('text' => !empty($info[2])));
114
            }
115
        }
116
    }
117
118
    /**
119
     * Returns an array of file data
120
     * @param string $hash
121
     * @return array
122
     */
123
    protected function parseFileHash($hash)
124
    {
125
        $parts = $this->parseHashTranslator($hash);
126
127
        if (empty($parts)) {
128
            return array();
129
        }
130
131
        list($module_id, $file) = $parts;
132
133
        if (gplcart_path_is_absolute($file)) {
134
            return array($file, "$module_id-" . basename($file));
135
        }
136
137
        return array();
138
    }
139
140
    /**
141
     * Parses a hash string containing the module ID and translation file
142
     * @param string $hash
143
     * @return array
144
     */
145
    protected function parseHashTranslator($hash)
146
    {
147
        $file = gplcart_path_absolute(gplcart_string_decode($hash));
148
149
        if (!$this->translator->isTranslationFile($file, $this->data_language['code'])) {
150
            return array();
151
        }
152
153
        $module_id = $this->translator->getModuleIdFromPath($file);
154
155
        if (empty($module_id)) {
156
            $module_id = 'core';
157
        }
158
159
        if ($module_id !== 'core' && !$this->module->get($module_id)) {
160
            return array();
161
        }
162
163
        return array($module_id, $file);
164
    }
165
166
    /**
167
     * Applies an action to the selected translation files
168
     */
169
    protected function actionFilesTranslator()
170
    {
171
        list($selected, $action) = $this->getPostedAction();
172
173
        $deleted = 0;
174
175
        foreach ($selected as $hash) {
176
            if ($action === 'delete' && $this->access('module_translator_delete')) {
177
                $deleted += (int) $this->deleteFileTranslator($hash);
178
            }
179
        }
180
181
        if ($deleted > 0) {
182
            $this->setMessage($this->text('Deleted %num item(s)', array('%num' => $deleted)), 'success');
183
        }
184
    }
185
186
    /**
187
     * Deletes a translation file
188
     * @param string $id
189
     * @return boolean
190
     */
191
    protected function deleteFileTranslator($id)
192
    {
193
        $info = $this->parseFileHash($id);
194
195
        if (isset($info[0])) {
196
            return $this->translator->delete($info[0], $this->data_language['code']);
197
        }
198
199
        return false;
200
    }
201
202
    /**
203
     * Returns an array of files for the language
204
     * @param string $tab
205
     * @return array
206
     */
207
    protected function getFilesTranslator($tab)
208
    {
209
        if ($tab === 'compiled') {
210
            return $this->getCompiledFilesTranslator();
211
        }
212
213
        return $this->getOriginalFilesTranslator();
214
    }
215
216
    /**
217
     * Sets titles on the file overview page
218
     */
219
    protected function setTitleFilesTranslator()
220
    {
221
        $this->setTitle($this->text('Translations for %name', array('%name' => $this->data_language['name'])));
222
    }
223
224
    /**
225
     * Sets breadcrumbs on the files overview page
226
     */
227
    protected function setBreadcrumbFilesTranslator()
228
    {
229
        $breadcrumb = array(
230
            'url' => $this->url('admin'),
231
            'text' => $this->text('Dashboard')
232
        );
233
234
        $this->setBreadcrumb($breadcrumb);
235
    }
236
237
    /**
238
     * Page callback
239
     * Displays the upload translation page
240
     * @param string $langcode
241
     */
242
    public function uploadTranslator($langcode)
243
    {
244
        $this->controlAccessUploadTranslator();
245
246
        $this->setLanguageTranslator($langcode);
247
        $this->setTitleUploadTranslator();
248
        $this->setBreadcrumbUploadTranslator();
249
250
        $this->setData('language', $this->data_language);
251
        $this->setData('modules', $this->getModulesTranslator());
252
        $this->setData('languages', $this->getLanguagesTranslator());
253
254
        $this->submitUploadTranslator();
255
        $this->outputUploadTranslator();
256
    }
257
258
    /**
259
     * Returns an array of sorted modules
260
     * @return array
261
     */
262
    protected function getModulesTranslator()
263
    {
264
        $modules = $this->module->getList();
265
        gplcart_array_sort($modules, 'name');
266
        return $modules;
267
    }
268
269
    /**
270
     * Controls permissions to upload a translation file
271
     */
272
    protected function controlAccessUploadTranslator()
273
    {
274
        $this->controlAccess('file_upload');
275
        $this->controlAccess('module_translator_upload');
276
    }
277
278
    /**
279
     * Sets titles on the upload translation page
280
     */
281
    protected function setTitleUploadTranslator()
282
    {
283
        $this->setTitle($this->text('Upload translation for %name', array('%name' => $this->data_language['name'])));
284
    }
285
286
    /**
287
     * Sets breadcrumbs on the upload translation page
288
     */
289
    protected function setBreadcrumbUploadTranslator()
290
    {
291
        $breadcrumb = array(
292
            'url' => $this->url('admin'),
293
            'text' => $this->text('Dashboard')
294
        );
295
296
        $this->setBreadcrumb($breadcrumb);
297
    }
298
299
    /**
300
     * Handles submission of translation file
301
     */
302
    protected function submitUploadTranslator()
303
    {
304
        if ($this->isPosted('save') && $this->validateUploadTranslator()) {
305
            $this->normalizeHtml();
306
            $this->copyFileTranslator();
307
        }
308
    }
309
310
    /**
311
     * Validates a uploaded translation file
312
     * @return boolean
313
     */
314
    protected function validateUploadTranslator()
315
    {
316
        $this->setSubmitted('translation');
317
        $this->validateUploadScopeTranslator();
318
        $this->validateUploadFileTranslator();
319
320
        return !$this->hasErrors();
321
    }
322
323
    /**
324
     * Validates scope of uploaded file
325
     * @return boolean
326
     */
327
    protected function validateUploadScopeTranslator()
328
    {
329
        $scope = $this->getSubmitted('scope');
330
331
        if (!empty($scope) && !$this->module->get($scope)) {
332
            $this->setError('scope', $this->text('@field has invalid value', array('@field' => $scope)));
333
            return false;
334
        }
335
336
        $this->setSubmitted('destination', $this->translation->getFile($this->data_language['code'], $scope));
337
        return true;
338
    }
339
340
    /**
341
     * Validates uploaded translation file
342
     * @return boolean|null
343
     */
344
    protected function validateUploadFileTranslator()
345
    {
346
        if ($this->isError()) {
347
            return null;
348
        }
349
350
        $file = $this->request->file('file');
351
352
        if (empty($file)) {
353
            $this->setError('file', $this->text('File is required'));
354
            return false;
355
        }
356
357
        $result = $this->file_transfer->upload($file, 'csv');
358
359
        if ($result !== true) {
360
            $this->setError('file', $result);
361
            return false;
362
        }
363
364
        $this->setSubmitted('file', $this->file_transfer->getTransferred());
365
        return true;
366
    }
367
368
    /**
369
     * Check and try to fix malformed HTML in the uploaded file
370
     */
371
    protected function normalizeHtml()
372
    {
373
        if ($this->isSubmitted('fix')) {
374
375
            $fixed = array();
376
            $file = $this->getSubmitted('file');
377
            $lines = $this->translator->getFixedStrings($file, $fixed);
378
379
            file_put_contents($file, ''); // Truncate the uploaded file
380
381
            foreach ($lines as $line) {
382
                gplcart_file_csv($file, $line);
383
            }
384
385
            if (!empty($fixed)) {
386
                $this->setMessage($this->text('Fixed @num rows', array('@num' => count($fixed))), 'warning', true);
387
            }
388
        }
389
    }
390
391
    /**
392
     * Copy a uploaded translation
393
     */
394
    protected function copyFileTranslator()
395
    {
396
        $this->controlAccessUploadTranslator();
397
398
        $source = $this->getSubmitted('file');
399
        $destination = $this->getSubmitted('destination');
400
401
        if (!$this->translator->copy($source, $destination)) {
402
            $this->redirect('', $this->text('Translation has not been saved'), 'warning');
403
        }
404
405
        $message = $this->text('Translation has been saved. Now you can <a href="@url">refresh language</a>', array(
406
            '@url' => $this->url('admin/settings/language')));
407
408
        $this->redirect('', $message, 'success');
409
    }
410
411
    /**
412
     * Render and output the upload translation page
413
     */
414
    protected function outputUploadTranslator()
415
    {
416
        $this->output('translator|upload');
417
    }
418
419
    /**
420
     * Render and output the file overview page
421
     */
422
    protected function outputFilesTranslator()
423
    {
424
        $this->output('translator|files');
425
    }
426
427
    /**
428
     * Returns an array of primary translations
429
     * @return array
430
     */
431
    protected function getOriginalFilesTranslator()
432
    {
433
        $files = array($this->translation->getFile($this->data_language['code']));
434
435
        foreach (array_keys($this->module->getList()) as $module_id) {
436
            $files[$module_id] = $this->translation->getFile($this->data_language['code'], $module_id);
437
        }
438
439
        foreach ($files as $id => $file) {
440
            if (is_file($file)) {
441
                $files[$id] = $this->buildFileInfoTranslator($file);
442
            } else {
443
                unset($files[$id]);
444
            }
445
        }
446
447
        return $files;
448
    }
449
450
    /**
451
     * Returns an array of compiled translation files
452
     * @return array
453
     */
454
    protected function getCompiledFilesTranslator()
455
    {
456
        $directory = $this->translation->getCompiledDirectory($this->data_language['code']);
457
458
        $files = array();
459
460
        if (is_dir($directory)) {
461
            foreach (gplcart_file_scan($directory, array('csv')) as $file) {
462
                $files[] = $this->buildFileInfoTranslator($file);
463
            }
464
        }
465
466
        return $files;
467
    }
468
469
    /**
470
     * Build translation file info
471
     * @param string $file
472
     * @return array
473
     */
474
    protected function buildFileInfoTranslator($file)
475
    {
476
        $path = gplcart_path_relative($file);
477
        $context = str_replace(array('-', '_'), array('/', '/*/'), pathinfo(basename($path), PATHINFO_FILENAME));
478
479
        $langcode = $this->data_language['code'];
480
        $js_file = $this->translation->getContextJsFile($langcode);
481
        $common_file = $this->translation->getCommonFile($langcode);
482
483
        if (substr($js_file, -strlen($path)) === $path) {
484
            $context = $this->text('No context');
485
        }
486
487
        if (substr($common_file, -strlen($path)) === $path) {
488
            $context = $this->text('No context');
489
        }
490
491
        return array(
492
            'path' => $path,
493
            'modified' => filemtime($file),
494
            'hash' => gplcart_string_encode($path),
495
            'filesize' => gplcart_file_size(filesize($file)),
496
            'context' => preg_replace('/\*\/$/', '*', $context),
497
            'progress' => $this->translator->getFileInfo($file)
498
        );
499
    }
500
501
    /**
502
     * Sets the current language
503
     * @param string $langcode
504
     */
505
    protected function setLanguageTranslator($langcode)
506
    {
507
        if (!isset($langcode)) {
508
            $langcode = $this->language->getDefault();
509
        }
510
511
        $this->data_language = $this->language->get($langcode);
512
513
        if (empty($this->data_language)) {
514
            $this->outputHttpStatus(404);
515
        }
516
    }
517
518
    /**
519
     * Page callback
520
     * Displays the translation view page
521
     * @param string $langcode
522
     * @param string $id
523
     */
524
    public function viewTranslator($langcode, $id)
525
    {
526
        $this->setLanguageTranslator($langcode);
527
        $this->setContentTranslator($id);
528
        $this->setTitleViewTranslator();
529
        $this->setBreadcrumbViewTranslator();
530
531
        $this->setData('strings', $this->data_content);
532
        $this->setData('language', $this->data_language);
533
534
        $this->outputViewTranslator();
535
    }
536
537
    /**
538
     * Read content from a translation file
539
     * @param string $hash
540
     */
541
    protected function setContentTranslator($hash)
542
    {
543
        $parsed = $this->parseHashTranslator($hash);
544
545
        if (empty($parsed)) {
546
            $this->outputHttpStatus(403);
547
        }
548
549
        $this->data_content = array();
550
551
        if (gplcart_path_is_absolute($parsed[1])) {
552
            $this->data_content = $this->translation->parseCsv($parsed[1]);
553
        }
554
555
        // Untranslated strings go first
556
        usort($this->data_content, function ($a) {
557
            return isset($a[1]) && $a[1] !== '' ? 1 : -1;
558
        });
559
    }
560
561
    /**
562
     * Sets titles on the translation view page
563
     */
564
    protected function setTitleViewTranslator()
565
    {
566
        $this->setTitle($this->text('Translation for %name', array('%name' => $this->data_language['name'])));
567
    }
568
569
    /**
570
     * Sets breadcrumbs on the translation view page
571
     */
572
    protected function setBreadcrumbViewTranslator()
573
    {
574
        $breadcrumb = array(
575
            'url' => $this->url('admin'),
576
            'text' => $this->text('Dashboard')
577
        );
578
579
        $this->setBreadcrumb($breadcrumb);
580
    }
581
582
    /**
583
     * Render and output the translation view page
584
     */
585
    protected function outputViewTranslator()
586
    {
587
        $this->output('translator|view');
588
    }
589
590
}
591