Passed
Push — master ( f23745...037cc0 )
by Andreas
12:12 queued 02:46
created

midcom_services_i18n_l10n::parse_data()   C

Complexity

Conditions 15
Paths 39

Size

Total Lines 71
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 15.3085

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 47
nc 39
nop 3
dl 0
loc 71
ccs 40
cts 45
cp 0.8889
crap 15.3085
rs 5.9166
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @package midcom.services
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
/**
10
 * This is the L10n main interface class, used by the components. It
11
 * allows you to get entries from the l10n string tables in the current
12
 * language with an automatic conversion to the destination character
13
 * set.
14
 *
15
 * <b>L10n language database file format specification:</b>
16
 *
17
 * Lines starting with --- are considered command lines and treated specially,
18
 * unless they occur within string data. All commands are separated with at
19
 * least a single space from their content, unless they don't have an argument.
20
 *
21
 * Empty lines are ignored, unless within string data.
22
 *
23
 * All keys and values will be trim'ed when encountered, so leading and trailing
24
 * whitespace will be eliminated completely.
25
 *
26
 * Windows-style line endings (\r\n) will be silently converted to the UNIX
27
 * \n style.
28
 *
29
 * Commented example:
30
 *
31
 * <pre>
32
 * ---# Lines starting with a # command are ignored.
33
 *
34
 * ---# File format version
35
 * ---VERSION 2.1.0
36
 *
37
 * ---# Language of the table
38
 * ---LANGUAGE en
39
 *
40
 * ---STRING string identifier
41
 * TRANSLATED STRING taken literally until ---STRINGEND, which is the
42
 * only reserved value at the beginning of the line, everything else is
43
 * fine. Linebreaks within the translation are preserved.
44
 * \r\n sequences are translated into \n
45
 * ---STRINGEND
46
 * </pre>
47
 *
48
 * File naming scheme: {$component_directory}/locale/default.{$lang}.txt
49
 *
50
 * @package midcom.services
51
 */
52
class midcom_services_i18n_l10n
53
{
54
    /**
55
     * The name of the locale library we use, this is usually
56
     * a component's name.
57
     */
58
    private string $_library;
59
60
    /**
61
     * The full path basename to the active library files. The individual
62
     * files are ending with .$lang.txt.
63
     */
64
    private string $_library_filename;
65
66
    /**
67
     * Fallback language, in case the selected language is not available.
68
     */
69
    private string $_fallback_language;
70
71
    /**
72
     * Current language.
73
     */
74
    private string $_language;
75
76
    /**
77
     * The string database
78
     */
79
    private array $_stringdb = [];
80
81
    /**
82
     * The current L10n DB file format number
83
     */
84
    private string $_version = '2.1.0';
85
86
    /**
87
     * The constructor loads the translation library indicated by the snippetdir
88
     * path $component and initializes the system completely. The output character
89
     * set will be initialized to the language's default.
90
     */
91 8
    public function __construct(string $component, string $language, string $fallback_language)
92
    {
93 8
        $this->_library = $component;
94 8
        $this->_fallback_language = $fallback_language;
95 8
        $this->_language = $language;
96 8
        $this->_library_filename = midcom::get()->componentloader->path_to_snippetpath($component) . "/locale/default";
97
    }
98
99
    /**
100
     * Load a language database
101
     *
102
     * - Leading and trailing whitespace will be eliminated
103
     */
104 8
    private function _load_language(string $lang) : array
105
    {
106 8
        $filename = "{$this->_library_filename}.{$lang}.txt";
107 8
        $identifier = str_replace('/', '-', $filename);
108
109 8
        if (midcom::get()->config->get('cache_module_memcache_backend') != 'flatfile') {
110 8
            $stringtable = midcom::get()->cache->memcache->get('L10N', $identifier);
111 8
            if (is_array($stringtable)) {
112 3
                return $stringtable;
113
            }
114
        }
115
116 8
        if (!file_exists($filename)) {
117 3
            return [];
118
        }
119
120 6
        $data = $this->parse_data(file($filename), $lang, $filename);
121
122
        // get site-specific l10n
123 6
        $snippet_path = "conf:/" . $this->_library . '/l10n/default.' . $lang . '.txt';
124 6
        if ($snippet_data = midcom_helper_misc::get_snippet_content_graceful($snippet_path)) {
125
            $data = array_merge($data, $this->parse_data(explode("\n", $snippet_data), $lang, $snippet_path));
126
        }
127
128 6
        if (midcom::get()->config->get('cache_module_memcache_backend') != 'flatfile') {
129 6
            midcom::get()->cache->memcache->put('L10N', $identifier, $data);
130
        }
131 6
        return $data;
132
    }
133
134 6
    private function parse_data(array $data, string $lang, string $filename) : array
135
    {
136 6
        $stringtable = [];
137 6
        $version = '';
138 6
        $language = '';
139 6
        $instring = false;
140 6
        $string_data = '';
141 6
        $string_key = '';
142
143 6
        foreach ($data as $line => $string) {
144
            // Kill any excess whitespace first.
145 6
            $string = trim($string);
146
147 6
            if (!$instring) {
148
                // outside of a string value
149 6
                if ($string == '') {
150 6
                    continue;
151
                }
152
153 6
                $command = preg_replace('/^---(.+?) .+/', '$1', $string);
154
155
                switch ($command) {
156 6
                    case '#':
157
                        // Skip
158 6
                        break;
159
160 6
                    case 'VERSION':
161 6
                        if ($version != '') {
162
                            throw $this->error("A second VERSION tag has been detected", $filename, $line);
163
                        }
164 6
                        $version = substr($string, 11);
165 6
                        break;
166
167 6
                    case 'LANGUAGE':
168 6
                        if ($language != '') {
169
                            throw $this->error("A second LANGUAGE tag has been detected", $filename, $line);
170
                        }
171 6
                        $language = substr($string, 12);
172 6
                        break;
173
174 6
                    case 'STRING':
175 6
                        $string_data = '';
176 6
                        $string_key = substr($string, 10);
177 6
                        $instring = true;
178 6
                        break;
179
180
                    default:
181 6
                        throw $this->error("Unknown command '{$command}'", $filename, $line);
182
                }
183 6
            } elseif ($string == '---STRINGEND') {
184 6
                $instring = false;
185 6
                $stringtable[$string_key] = $string_data;
186 6
            } elseif ($string_data == '') {
187 6
                $string_data .= $string;
188
            } else {
189 1
                $string_data .= "\n{$string}";
190
            }
191
        }
192
193 6
        if ($instring) {
194
            throw new midcom_error("L10n DB SYNTAX ERROR: String constant exceeds end of file.");
195
        }
196 6
        if (version_compare($version, $this->_version, "<")) {
197
            throw new midcom_error("L10n DB ERROR: File format version of {$filename} is too old, no update available at the moment.");
198
        }
199 6
        if ($lang != $language) {
200
            throw new midcom_error("L10n DB ERROR: The DB language version {$language} did not match the requested {$lang}.");
201
        }
202
203 6
        ksort($stringtable, SORT_STRING);
204 6
        return $stringtable;
205
    }
206
207
    private function error(string $message, string $filename, int $line) : midcom_error
208
    {
209
        $line++; // Array is 0-indexed
210
        return new midcom_error('L10n DB SYNTAX ERROR: ' .  $message . ' at ' . $filename . ' ' . $line);
211
    }
212
213
    /**
214
     * Checks, whether the referenced language is already loaded. If not,
215
     * it is automatically made available.
216
     */
217 387
    private function _check_for_language(string $lang)
218
    {
219 387
        if (!array_key_exists($lang, $this->_stringdb)) {
220 8
            $this->_stringdb[$lang] = $this->_load_language($lang);
221
        }
222
    }
223
224
    /**
225
     * Set output language.
226
     *
227
     * This is usually set through midcom_services_i18n.
228
     */
229 3
    public function set_language(string $lang)
230
    {
231 3
        $this->_language = $lang;
232
    }
233
234
    /**
235
     * Set the fallback language.
236
     *
237
     * This is usually set through midcom_services_i18n.
238
     */
239
    public function set_fallback_language(string $lang)
240
    {
241
        $this->_fallback_language = $lang;
242
    }
243
244 61
    public function get_formatter() : midcom_services_i18n_formatter
245
    {
246 61
        return new midcom_services_i18n_formatter($this->_language);
247
    }
248
249
    /**
250
     * Checks if a localized string for $string exists. If $language is unset,
251
     * the current language is used.
252
     *
253
     * @param string $language The language to search in.
254
     */
255 387
    function string_exists(string $string, string $language = null) : bool
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
256
    {
257 387
        $language ??= $this->_language;
258 387
        $this->_check_for_language($language);
259
260 387
        return isset($this->_stringdb[$language][$string]);
261
    }
262
263
    /**
264
     * Checks whether the given string is available in either the current
265
     * or the fallback language. Use this to determine if an actually processed
266
     * result is returned by get. This is helpful especially if you want to
267
     * "catch" cases where a string might translate to itself in some languages.
268
     */
269 130
    public function string_available(string $string) : bool
270
    {
271 130
        return $this->string_exists($string, $this->_language)
272 130
            || $this->string_exists($string, $this->_fallback_language);
273
    }
274
275
    /**
276
     * Retrieves a localized string from the database using $language as
277
     * destination. If $language is unset, the currently set default language is
278
     * used. If the string is not found in the selected language, the fallback
279
     * is checked. If even the fallback cannot be found, then $string is
280
     * returned and the event is logged to MidCOMs Debugging system.
281
     *
282
     * L10n DB loads are done through string_exists.
283
     *
284
     * @param string $language The language to search in, uses the current language as default.
285
     */
286 385
    public function get(string $string, $language = null) : string
287
    {
288 385
        $language ??= $this->_language;
289
290 385
        if (!$this->string_exists($string, $language)) {
291
            // Go for Fallback
292 123
            $language = $this->_fallback_language;
293
294 123
            if (!$this->string_exists($string, $language)) {
295
                // Nothing found, log is produced by string_exists.
296 122
                return $string;
297
            }
298
        }
299
300 379
        return midcom::get()->i18n->convert_from_utf8($this->_stringdb[$language][$string]);
301
    }
302
303
    /**
304
     * This is a shortcut for "echo $this->get(...);", useful in style code.
305
     *
306
     * Note, that due to the stupidity of the Zend engine, it is not possible to call
307
     * this function echo, like it should have been called.
308
     *
309
     * @param string $language The language to search in, uses the current language as default.
310
     * @see get()
311
     */
312 14
    public function show(string $string, $language = null)
313
    {
314 14
        echo $this->get($string, $language);
315
    }
316
}
317