Passed
Pull Request — master (#81)
by Dante
01:14
created

File::fixString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 8
rs 10
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2023 Atlas Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
16
namespace BEdita\I18n\Filesystem;
17
18
use RecursiveDirectoryIterator;
19
use RecursiveIteratorIterator;
20
use RegexIterator;
21
22
class File
23
{
24
    /**
25
     * Parse a directory
26
     *
27
     * @param string $dir The directory
28
     * @param string $defaultDomain The default domain
29
     * @param array $translations The translations array
30
     * @return bool
31
     */
32
    public static function parseDir(string $dir, string $defaultDomain, array &$translations): bool
33
    {
34
        $result = true;
35
        if (!is_dir($dir)) {
36
            return false;
37
        }
38
        $files = new RegexIterator(
39
            new RecursiveIteratorIterator(
40
                new RecursiveDirectoryIterator(
41
                    $dir,
42
                    RecursiveDirectoryIterator::KEY_AS_PATHNAME | RecursiveDirectoryIterator::CURRENT_AS_PATHNAME
43
                )
44
            ),
45
            '/.*\.(php|ctp|thtml|inc|tpl|twig)/i',
46
        );
47
        foreach ($files as $file) {
48
            $parseResult = self::parseFile($file, $defaultDomain, $translations);
49
            $result = $result && $parseResult;
50
        }
51
52
        return $result;
53
    }
54
55
    /**
56
     * Parse file and rips gettext strings
57
     *
58
     * @param string $file The file name
59
     * @param string $defaultDomain The default domain
60
     * @param array $translations The translations array
61
     * @return bool
62
     */
63
    public static function parseFile(string $file, string $defaultDomain, array &$translations): bool
64
    {
65
        if (!file_exists($file)) {
66
            return false;
67
        }
68
        $content = file_get_contents($file);
69
        if (empty($content)) {
70
            return false;
71
        }
72
73
        $functions = [
74
            '__' => 0, // __( string $singular , ... $args )
75
            '__n' => 0, // __n( string $singular , string $plural , integer $count , ... $args )
76
            '__d' => 1, // __d( string $domain , string $msg , ... $args )
77
            '__dn' => 1, // __dn( string $domain , string $singular , string $plural , integer $count , ... $args )
78
            '__x' => 1, // __x( string $context , string $singular , ... $args )
79
            '__xn' => 1, // __xn( string $context , string $singular , string $plural , integer $count , ... $args )
80
            '__dx' => 2, // __dx( string $domain , string $context , string $msg , ... $args )
81
            '__dxn' => 2, // __dxn( string $domain , string $context , string $singular , string $plural , integer $count , ... $args )
82
        ];
83
84
        // temporarily replace "\'" with "|||||", fixString will replace "|||||" with "\'"
85
        // this fixes wrongly matched data in the following regexp
86
        $content = str_replace("\'", '|||||', $content);
87
88
        $options = [
89
            'open_parenthesis' => preg_quote('('),
90
            'quote' => preg_quote("'"),
91
            'double_quote' => preg_quote('"'),
92
        ];
93
94
        foreach ($functions as $fname => $singularPosition) {
95
            $capturePath = "'[^']*'";
96
            $doubleQuoteCapture = str_replace("'", $options['double_quote'], $capturePath);
97
            $quoteCapture = str_replace("'", $options['quote'], $capturePath);
98
99
            // phpcs:disable
100
            $rgxp = '/' . $fname . '\s*' . $options['open_parenthesis'] . str_repeat('((?:' . $doubleQuoteCapture . ')|(?:' . $quoteCapture . '))\s*[,)]\s*', $singularPosition + 1) . '/';
101
            // phpcs:enable
102
103
            $matches = [];
104
            preg_match_all($rgxp, $content, $matches);
105
106
            $limit = count($matches[0]);
107
            for ($i = 0; $i < $limit; $i++) {
108
                $domain = $defaultDomain;
109
                $ctx = '';
110
                $str = self::unquoteString($matches[1][$i]);
111
112
                if (strpos($fname, '__d') === 0) {
113
                    $domain = self::unquoteString($matches[1][$i]);
114
115
                    if (strpos($fname, '__dx') === 0) {
116
                        $ctx = self::unquoteString($matches[2][$i]);
117
                        $str = self::unquoteString($matches[3][$i]);
118
                    } else {
119
                        $str = self::unquoteString($matches[2][$i]);
120
                    }
121
                } elseif (strpos($fname, '__x') === 0) {
122
                    $ctx = self::unquoteString($matches[1][$i]);
123
                    $str = self::unquoteString($matches[2][$i]);
124
                }
125
                $str = self::fixString($str);
126
127
                if (!array_key_exists($domain, $translations)) {
128
                    $translations[$domain] = [];
129
                }
130
131
                if (!array_key_exists($str, $translations[$domain])) {
132
                    $translations[$domain][$str] = [''];
133
                }
134
135
                if (!in_array($ctx, $translations[$domain][$str])) {
136
                    $translations[$domain][$str][] = $ctx;
137
                }
138
            }
139
        }
140
141
        return true;
142
    }
143
144
    /**
145
     * Remove leading and trailing quotes from string
146
     *
147
     * @param string $str The string
148
     * @return string The new string
149
     */
150
    public static function unquoteString($str): string
151
    {
152
        return substr($str, 1, -1);
153
    }
154
155
    /**
156
     * "fix" string - strip slashes, escape and convert new lines to \n
157
     *
158
     * @param string $str The string
159
     * @return string The new string
160
     */
161
    public static function fixString($str): string
162
    {
163
        $str = stripslashes($str);
164
        $str = str_replace('"', '\"', $str);
165
        $str = str_replace("\n", '\n', $str);
166
        $str = str_replace('|||||', "'", $str); // special sequence used in parseContent to temporarily replace "\'"
167
168
        return $str;
169
    }
170
}
171