Completed
Push — development ( cfd391...deed4d )
by Andrij
12:03
created

LangReplacer::parseDir()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 5
nop 1
dl 0
loc 22
rs 6.9811
c 0
b 0
f 0
1
<?php
2
3
namespace translator\classes;
4
5
use Gettext\Translation;
6
use Gettext\Translations;
7
8
class LangReplacer
9
{
10
11
    public static $domainTranslations = [];
12
13
    const LANG_FUNC = '(t?langf?)';
14
15
    const WORD = '([\s\S]+?)';
16
17
    const QUOTE = '[\'\"]{1}';
18
19
    const QUOTED_WORD = self::QUOTE . self::WORD . self::QUOTE;
20
21
    const QUOTED_DOMAIN = '(?:\s*,\s*' . self::QUOTE . self::WORD . self::QUOTE . ')?';
22
23
    const QUOTED_PARAMS = '(?:\s*,\s*' . self::WORD . ')?';
24
25
    const EXPRESSION = '/' . self::LANG_FUNC . '\(' . self::QUOTED_WORD . self::QUOTED_DOMAIN . '\)/';
26
27
28
    /*
29
     *******************************************************************************************************************
30
     * INTERFACE
31
     *
32
     * USAGE
33
     *  $module = 'aggregator';
34
     *  $allModules
35
     *  LangReplacer::replaceModuleLangStrings($module);
36
     *  LangReplacer::replaceMainLangStrings();
37
     *  LangReplacer::findCyrillicKeys($module)
38
     *******************************************************************************************************************
39
     */
40
41
    public static function findCyrillicKeys($module) {
42
        if ($module == 'main') {
43
            $langStrings = self::parseMain();
44
        } else {
45
            $langStrings = self::parseModule($module);
46
        }
47
        $found = [];
48
49
        foreach ($langStrings as $file => $found) {
50
            foreach (array_keys($found) as $key) {
51
52
                if (self::isCyrillic($key)) {
53
                    dump($file, $key);
0 ignored issues
show
Coding Style introduced by
The use of function dump() is forbidden
Loading history...
54
                }
55
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
56
            }
57
        }
58
59
    }
60
61
    public static function replaceMainLangStrings() {
62
63
        $mainLangStrings = self::parseMain();
64
65
        $module = 'main';
66
        foreach ($mainLangStrings as $file => $data) {
67
            $translated = self::changeKeysInFile($file, $data);
68
            self::updateKeysInMoPoFiles($translated, $module);
69
        }
70
        self::savePoMo($module);
71
    }
72
73
    /**
74
     * Replace key to english translation
75
     * Add translation to other languages for this key
76
     * @param $module
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
77
     */
78
    public static function replaceModuleLangStrings($module) {
79
80
        $moduleLangStrings = self::parseModule($module);
81
82
        foreach ($moduleLangStrings as $file => $data) {
83
            $translated = self::changeKeysInFile($file, $data);
84
            self::updateKeysInMoPoFiles($translated, $module);
85
        }
86
87
    }
88
89
    private static function savePoMo($saveDomain) {
90
        foreach (self::$domainTranslations as $domain => $langs) {
91
            if ($domain == $saveDomain) {
92
93
                foreach ($langs as $lang => $translation) {
94
                    $dir = self::getDomainPoFilePath($domain, $lang);
95
                    dump(sprintf('Po file saving.. : %s', $dir));
0 ignored issues
show
Bug introduced by
The call to dump() misses a required argument $valid.

This check looks for function calls that miss required arguments.

Loading history...
Coding Style introduced by
The use of function dump() is forbidden
Loading history...
96
                    $translation->toPoFile($dir);
97
                    $dir = self::createDomainMoFilePath($domain, $lang);
98
                    $translation->toMoFile($dir);
99
                    dump(sprintf('Mo file saving.. : %s', $dir));
0 ignored issues
show
Bug introduced by
The call to dump() misses a required argument $valid.

This check looks for function calls that miss required arguments.

Loading history...
Coding Style introduced by
The use of function dump() is forbidden
Loading history...
100
                }
101
            }
102
        }
103
    }
104
105
    /**
106
     *
107
     * @param array $translated
108
     * @param string $module
109
     */
110
    private static function updateKeysInMoPoFiles($translated, $module) {
111
112
        $languages = self::getModuleLanguages($module);
113
        foreach ($languages as $language) {
114
            $translationsAllLocales[$language] = self::getDomainTranslations($module, $language);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$translationsAllLocales was never initialized. Although not strictly required by PHP, it is generally a good practice to add $translationsAllLocales = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
115
        }
116
117
        foreach ($translated as $changed) {
118
            foreach ($changed as $one) {
119
                foreach ($translationsAllLocales as $lang => $translationsOneLocale) {
120
                    /** @var Translation $oldTranslation */
121
                    $oldTranslation = $translationsOneLocale->find('', $one['from']);
122
123
                    if ($oldTranslation) {
124
                        $newTranslation = new Translation('', $one['to']);
125
                        $newTranslation->mergeWith($oldTranslation);
126
                        $translationsOneLocale[] = $newTranslation;
127
128
                        if (!$newTranslation->getTranslation()) {
129
                            $newTranslation->setTranslation($one['from']);
130
                        }
131
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
132
                    }
133
                }
134
            }
135
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
136
        }
137
138
    }
139
140
    /**
141
     * @param string $file
142
     * @param array $data
143
     * @return array
144
     */
145
    private static function changeKeysInFile($file, $data) {
146
        $translated = [];
147
        if (is_writable($file)) {
148
            $content = $newContent = file_get_contents($file);
149
            foreach ($data as $signature => $data) {
150
                $function = $data['function'];
151
                $word = $data['string'];
152
                $domain = $data['domain'];
153
                $params = $data['params'];
154
                $translation = self::translate($domain, $word, 'en_US');
155
                if ($translation && $translation !== $word) {
156
                    $newSignature = self::createReplacement($function, $translation, $domain, $params);
157
                    $translated[$domain][] = [
158
                                              'from'   => $word,
159
                                              'to'     => $translation,
160
                                              'domain' => $domain,
161
                                             ];
162
                    dump(sprintf('%s replaced to %s', $signature, $newSignature));
0 ignored issues
show
Bug introduced by
The call to dump() misses a required argument $valid.

This check looks for function calls that miss required arguments.

Loading history...
Coding Style introduced by
The use of function dump() is forbidden
Loading history...
163
                    $newContent = str_replace($signature, $newSignature, $newContent);
164
                    //                    dump(sprintf('Changed %s to %s', $word, $translation));
165
                } else {
166
                    dump(sprintf("No default translation for: '%s' in domain %s", $word, $domain));
0 ignored issues
show
Bug introduced by
The call to dump() misses a required argument $valid.

This check looks for function calls that miss required arguments.

Loading history...
Coding Style introduced by
The use of function dump() is forbidden
Loading history...
167
                }
168
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
169
            }
170
171
            if ($newContent && $newContent !== $content) {
172
                dump(sprintf('File %s saved: %s', $file, file_put_contents($file, $newContent) ? 'TRUE' : 'FALSE'));
0 ignored issues
show
Bug introduced by
The call to dump() misses a required argument $valid.

This check looks for function calls that miss required arguments.

Loading history...
Coding Style introduced by
The use of function dump() is forbidden
Loading history...
173
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
174
                //                dump("Warning: {$file} no content or no changes");
175
            }
176
        } else {
177
            dump("File: {$file} is not writable");
0 ignored issues
show
Bug introduced by
The call to dump() misses a required argument $valid.

This check looks for function calls that miss required arguments.

Loading history...
Coding Style introduced by
The use of function dump() is forbidden
Loading history...
178
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
179
        }
180
181
        return $translated;
182
183
    }
184
185
    public static function getMainPaths() {
186
        return [
187
                APPPATH . 'core',
188
                APPPATH . 'errors',
189
                APPPATH . 'helpers',
190
                APPPATH . 'libraries',
191
                APPPATH . 'modules/shop/classes',
192
                APPPATH . 'modules/shop/helpers',
193
194
                PUBPATH . 'system/language/form_validation',
195
                PUBPATH . 'system/language/email_lang',
196
                PUBPATH . 'system/language/upload',
197
                PUBPATH . 'system/libraries',
198
                PUBPATH . 'templates/administrator/js/jquery-validate',
199
                PUBPATH . 'application/modules/shop/widgets',
200
                PUBPATH . 'application/modules/shop/models',
201
202
               ];
203
    }
204
205
    /*
206
     *******************************************************************************************************************
207
     * PARSE
208
     *******************************************************************************************************************
209
     */
210
211
    /**
212
     * Find lang calls in module files
213
     *
214
     * @param $module
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
215
     * @return array
216
     */
217
    public static function parseModule($module) {
218
        return self::parseDir(self::getModulePath($module));
219
    }
220
221
    /**
222
     * Find all main lang calls in main directories
223
     * @return array
224
     */
225
    public static function parseMain() {
226
227
        $results = [];
228
        foreach (self::getMainPaths() as $mainPath) {
229
230
            $add = self::parseDir($mainPath);
231
            if (is_array($add)) {
232
                $results = array_merge($results, $add);
233
            }
234
        }
235
        return $results;
236
    }
237
238
    /**
239
     * Search lang function call in all php|tpl files recursively
240
     * @param $dir
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
241
     * @return array
242
     */
243
    public static function parseDir($dir) {
244
        $dirIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
245
        $results = [];
246
        /** @var \SplFileInfo $item */
247
        foreach ($dirIterator as $item) {
248
            if ($item->isFile() && in_array($item->getExtension(), ['php', 'tpl']) && !strstr($item->getBasename(), 'jsLangs')) {
249
                $res = self::find(file_get_contents($item->getRealPath()));
250
                if (count($res[0])) {
251
                    foreach ($res[0] as $key => $signature) {
252
                        $results[$item->getRealPath()][$signature] = [
253
                                                                      'function' => $res[1][$key],
254
                                                                      'string'   => $res[2][$key],
255
                                                                      'domain'   => $res[3][$key],
256
                                                                      'params'   => $res[4][$key],
257
                                                                     ];
258
                    }
259
                }
260
            }
261
        }
262
263
        return $results;
264
    }
265
266
    /*
267
     *******************************************************************************************************************
268
     *  Translate
269
     *******************************************************************************************************************
270
     */
271
272
    /**
273
     * @param $domain
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
274
     * @param $word
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
275
     * @param string $lang
276
     * @return null|string
277
     */
278
    public static function translate($domain, $word, $lang = 'en_US') {
279
280
        $domain = $domain ?: 'main';
281
        $translations = self::getDomainTranslations($domain, $lang);
282
283
        $translation = $translations->find(null, $word);
284
285
        return $translation ? $translation->getTranslation() : null;
286
    }
287
288
    /**
289
     * @param $domain
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
290
     * @param string $locale
291
     * @return Translations
292
     */
293
    public static function getDomainTranslations($domain, $locale = 'en_US') {
294
295
        if (!isset(self::$domainTranslations[$domain][$locale])) {
296
            self::$domainTranslations[$domain][$locale] = Translations::fromPoFile(self::getDomainPoFilePath($domain, $locale));
297
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
298
        }
299
        return self::$domainTranslations[$domain][$locale];
300
    }
301
302
    /*
303
     *******************************************************************************************************************
304
     * REGEXP
305
     *******************************************************************************************************************
306
     */
307
308
    /**
309
     * Find all language calls in string
310
     * @param $fileContent
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
311
     * @param string $word
312
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string[][]?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
313
     */
314
    public static function find($fileContent, $word = '') {
315
        $expression = self::createExpression($word);
316
        preg_match_all($expression, $fileContent, $matches);
317
        return $matches;
318
    }
319
320
    public static function isCyrillic($word) {
321
        return preg_match('/[\p{Cyrillic}]/u', $word) || preg_match('/[А-Яа-яЁё]/u', $word);
322
    }
323
324
    /**
325
     * Create lang function call
326
     * @param $function
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
327
     * @param $word
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
328
     * @param $domain
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
329
     * @param $params
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
330
     * @return string
331
     */
332
    public static function createReplacement($function, $word, $domain, $params) {
333
        return sprintf("%s('%s'%s%s)", $function, $word, $domain ? sprintf(", '%s'", $domain) : '', $params ? sprintf(', %s', $params) : '');
334
    }
335
336
    /**
337
     * Create regular expression for lang call
338
     * @param $word
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
339
     * @return string
340
     */
341
    private static function createExpression($word = null) {
342
343
        $word = $word ? "($word)" : self::WORD;
344
        $quotedWord = self::QUOTE . $word . self::QUOTE;
345
        $expression = '/' . self::LANG_FUNC . '\(' . $quotedWord . self::QUOTED_DOMAIN . self::QUOTED_PARAMS . '\)/';
346
347
        return $expression;
348
349
    }
350
351
    /*
352
     *******************************************************************************************************************
353
     * PATHS
354
     *******************************************************************************************************************
355
     */
356
357
    /**
358
     * Get lang directories for module
359
     *
360
     * @param $module
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
361
     * @return array ['en_US', ...]
362
     */
363
    public static function getModuleLanguages($module) {
364
        $languages = [];
365
366 View Code Duplication
        if (in_array($module, ['shop', '', 'main'])) {
367
            $path = PUBPATH . APPPATH . 'language/main/*';
368
        } else {
369
            $path = self::getModulePath($module) . '/language/*';
370
        }
371
372
        foreach (glob($path) as $item) {
373
            $languages[] = substr($item, strrpos($item, '/') + 1);
374
        }
375
        return $languages;
376
    }
377
378
    /**
379
     * Get po file path
380
     * @param string $domain
381
     * @param string $locale
382
     * @return string
383
     */
384 View Code Duplication
    public static function getDomainPoFilePath($domain, $locale = 'en_US') {
385
        if (in_array($domain, ['shop', '', 'main'])) {
386
            $domain = 'main';
387
        }
388
389
        $dir = self::getTranslationDir($domain, $locale);
390
        return sprintf('%s%s.po', $dir, $domain);
391
392
    }
393
394
    /**
395
     * Create new mo file path
396
     * @param $domain
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
397
     * @param string $locale
398
     * @return string
399
     */
400 View Code Duplication
    public static function createDomainMoFilePath($domain, $locale = 'en_US') {
401
        if (in_array($domain, ['shop', '', 'main'])) {
402
            $domain = 'main';
403
        }
404
405
        $dir = self::getTranslationDir($domain, $locale);
406
        return sprintf('%s%s_%s.mo', $dir, $domain, time());
407
    }
408
409
    /**
410
     * Languages directory
411
     *
412
     * @param $domain
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
413
     * @param $locale
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
414
     * @return string
415
     */
416
    public static function getTranslationDir($domain, $locale) {
417 View Code Duplication
        if (in_array($domain, ['shop', '', 'main'])) {
418
            $transPath = PUBPATH . APPPATH . "language/main/{$locale}/LC_MESSAGES/";
419
        } else {
420
            $transPath = self::getModulePath($domain) . "/language/{$locale}/LC_MESSAGES/";
421
        }
422
        return $transPath;
423
    }
424
425
    /**
426
     * Full path to module
427
     * @param $moduleName
0 ignored issues
show
introduced by
Missing parameter type
Loading history...
428
     * @return string
429
     */
430
    public static function getModulePath($moduleName) {
431
        return PUBPATH . APPPATH . 'modules/' . $moduleName;
432
    }
433
434
    public static function getAllModules() {
435
        $modules = [];
436
        $paths = PUBPATH . APPPATH . 'modules/*';
437
438
        foreach (glob($paths) as $path) {
439
440
            $modules[] = substr($path, strrpos($path, '/') + 1);
441
        }
442
443
        return $modules;
444
    }
445
446
}