Completed
Push — master ( fb45d5...16ca95 )
by Iurii
01:17
created

Translator::getOriginalFilesTranslator()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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