Passed
Push — master ( a53729...980ce8 )
by Andreas
21:34
created

midcom_services_i18n::html_entity_decode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
use Symfony\Component\Intl\Intl;
10
use Symfony\Component\Intl\Locales;
11
use Symfony\Component\HttpFoundation\RequestStack;
12
use Symfony\Component\HttpFoundation\Request;
13
use Symfony\Component\Translation\Translator;
14
15
/**
16
 * This is a basic MidCOM Service which provides an interfaces to the
17
 * various I18n facilities of MidCOM.
18
 *
19
 * The I18n service serves as a central access point for all aspects
20
 * around internationalization and localization. It provides auto-detection
21
 * of language data using HTTP Content-Negotiation along with a cookie-based
22
 * fallback.
23
 *
24
 * This class is able to run independently from midcom_application
25
 * due to the fact that it is used in the cache_hit code.
26
 *
27
 * Use this class to set the language preferences (charset and locale) and to gain
28
 * access to the l10n string databases. A few helpers which can be used to ease
29
 * translation work (like charset conversion) are in here as well.
30
 *
31
 * All language codes used here are ISO 639-1 two-letter codes.
32
 *
33
 * @package midcom.services
34
 */
35
class midcom_services_i18n
36
{
37
    /**
38
     * @var string
39
     */
40
    private $_fallback_language;
41
42
    /**
43
     * @var midcom_services_i18n_l10n[]
44
     */
45
    private $_obj_l10n = [];
46
47
    /**
48
     * @var string
49
     */
50
    private $_current_language;
51
52
    /**
53
     * @var string
54
     */
55
    private $_current_charset = 'utf-8';
56
57
    /**
58
     * Initialize the available i18n framework by determining the desired language
59
     * from these different sources: HTTP Content Negotiation, Client side language cookie.
60
     *
61
     * Its two parameters set the default language in case that none is supplied
62
     * via HTTP Content Negotiation or through Cookies.
63
     *
64
     * The default language set on startup is currently hardcoded to 'en',
65
     * you should override it after initialization, if you want something
66
     * else using the setter methods below.
67
     *
68
     * The fallback language is read from the MidCOM configuration directive
69
     * <i>i18n_fallback_language</i>.
70
     */
71 3
    public function __construct(RequestStack $request_stack, string $fallback_language)
72
    {
73 3
        $this->_fallback_language = $fallback_language;
74
75 3
        $found = false;
76 3
        if ($request = $request_stack->getCurrentRequest()) {
77 2
            $found = $this->_read_cookie($request) || $this->_read_http_negotiation($request);
78
        }
79 3
        if (!$found) {
80 1
            $this->set_language($fallback_language);
81
        }
82
    }
83
84
    /**
85
     * Try to pull the user's preferred language and
86
     * character set out of a cookie named "midcom_services_i18n".
87
     */
88 2
    private function _read_cookie(Request $request) : bool
89
    {
90 2
        if (!$request->cookies->has('midcom_services_i18n')) {
91 2
            return false;
92
        }
93
94
        $rawdata = base64_decode($request->cookies->get('midcom_services_i18n'));
95
        $array = unserialize($rawdata);
96
97
        if (   !array_key_exists('language', $array)
98
            || !array_key_exists('charset', $array)) {
99
            debug_add("Rejecting cookie, it seems invalid.");
100
            return false;
101
        }
102
        $this->set_charset($array['charset']);
103
        return $this->set_language($array['language']);
104
    }
105
106
    /**
107
     * Pull available language out of the HTTP Headers
108
     *
109
     * q-parameters for prioritization are supported.
110
     */
111 2
    private function _read_http_negotiation(Request $request) : bool
112
    {
113 2
        foreach ($request->getLanguages() as $lang) {
114
            // we can't use strings like en_US, so we only use the first two characters
115 2
            $lang = substr($lang, 0, 2);
116 2
            if ($this->set_language($lang)) {
117 2
                return true;
118
            }
119
        }
120
        return false;
121
    }
122
123 1
    public function get_translator(array $prefixes) : Translator
124
    {
125 1
        $locale = $this->get_current_language();
126 1
        $translator = new Translator($locale);
127 1
        foreach ($prefixes as $prefix) {
128 1
            $translator->addResource('xlf', $prefix . $locale . '.xlf', $locale);
129
        }
130 1
        return $translator;
131
    }
132
133
    /**
134
     * Set output character set.
135
     */
136
    public function set_charset(string $charset)
137
    {
138
        $this->_current_charset = strtolower($charset);
139
    }
140
141
    /**
142
     * Set output language.
143
     *
144
     * This will set the character encoding to the language's default
145
     * encoding and will also set the system locale to the one
146
     * specified in the language database.
147
     *
148
     * If you want another character encoding as the default one, you
149
     * have to override it manually using midcom_services_i18n::set_charset()
150
     * after calling this method.
151
     *
152
     * @param string $lang    Language ISO 639-1 code
153
     */
154 3
    public function set_language(string $lang) : bool
155
    {
156 3
        if (Locales::getName($lang) === null) {
0 ignored issues
show
introduced by
The condition Symfony\Component\Intl\L...getName($lang) === null is always false.
Loading history...
157
            debug_add("Language {$lang} not found.", MIDCOM_LOG_ERROR);
158
            return false;
159
        }
160
161 3
        $this->_current_language = $lang;
162
163 3
        setlocale(LC_ALL, $lang);
164 3
        if (Intl::isExtensionLoaded()) {
165 3
            Locale::setDefault($lang);
166
        }
167
168 3
        foreach ($this->_obj_l10n as $object) {
169
            $object->set_language($lang);
170
        }
171 3
        return true;
172
    }
173
174
    /**
175
     * Set the fallback language.
176
     */
177
    public function set_fallback_language(string $lang)
178
    {
179
        $this->_fallback_language = $lang;
180
        foreach ($this->_obj_l10n as $object) {
181
            $object->set_fallback_language($lang);
182
        }
183
    }
184
185
    /**
186
     * Returns the current language code
187
     */
188 397
    public function get_current_language() : string
189
    {
190 397
        return $this->_current_language;
191
    }
192
193
    /**
194
     * Returns the current fallback language code
195
     */
196 51
    public function get_fallback_language() : string
197
    {
198 51
        return $this->_fallback_language;
199
    }
200
201
    /**
202
     * Returns the current character set
203
     */
204 108
    public function get_current_charset() : string
205
    {
206 108
        return $this->_current_charset;
207
    }
208
209
    /**
210
     * Returns a l10n class instance which can be used to
211
     * access the localization data of the current component.
212
     *
213
     * If loading failed, midcom_error is thrown, otherwise the l10n
214
     * db cache is populated accordingly.
215
     *
216
     * @see midcom_services_i18n_l10n
217
     */
218 406
    public function get_l10n(string $component = 'midcom') : midcom_services_i18n_l10n
219
    {
220 406
        if (!array_key_exists($component, $this->_obj_l10n)) {
221 5
            $this->_obj_l10n[$component] = new midcom_services_i18n_l10n($component, $this->_current_language, $this->_fallback_language);
222
        }
223
224 406
        return $this->_obj_l10n[$component];
225
    }
226
227
    /**
228
     * Returns a translated string using the l10n database specified in the function
229
     * arguments.
230
     *
231
     * @see midcom_services_i18n_l10n::get()
232
     */
233 370
    public function get_string(string $stringid, string $component) : string
234
    {
235
        try {
236 370
            return $this->get_l10n($component)->get($stringid);
237
        } catch (midcom_error $e) {
238
            $e->log(MIDCOM_LOG_WARN);
239
            return $stringid;
240
        }
241
    }
242
243
    /**
244
     * This is a shortcut for echo $this->get_string(...);.
245
     *
246
     * To keep the naming stable with the actual l10n class, this is not called
247
     * echo_string (Zend won't allow $l10n->echo().)
248
     *
249
     * @see midcom_services_i18n_l10n::get()
250
     * @see get_string()
251
     */
252
    public function show_string(string $stringid, string $component)
253
    {
254
        echo $this->get_string($stringid, $component);
255
    }
256
257
    /**
258
     * This is a calling wrapper to the iconv library.
259
     *
260
     * See the PHP iconv() function for the exact parameter definitions.
261
     *
262
     * @return mixed The converted string or false on any error.
263
     */
264
    private function iconv(string $source_charset, string $destination_charset, string $string)
265
    {
266
        $result = @iconv($source_charset, $destination_charset, $string);
267
        if ($result === false && !empty($string)) {
268
            debug_add("Iconv returned failed to convert a string, returning an empty string.", MIDCOM_LOG_WARN);
269
            debug_print_r("Tried to convert this string from {$source_charset} to {$destination_charset}:", $string);
270
            midcom::get()->debug->log_php_error(MIDCOM_LOG_WARN);
271
            return false;
272
        }
273
        return $result;
274
    }
275
276
    /**
277
     * Convert a string assumed to be in the currently active charset to UTF8.
278
     *
279
     * @return string The string converted to UTF-8
280
     */
281
    public function convert_to_utf8(string $string)
282
    {
283
        if ($this->_current_charset == 'utf-8') {
284
            return $string;
285
        }
286
        return $this->iconv($this->_current_charset, 'utf-8', $string);
287
    }
288
289
    /**
290
     * Convert a string assumed to be in UTF-8 to the currently active charset.
291
     *
292
     * @return string The string converted to the current charset
293
     */
294 381
    public function convert_from_utf8(string $string)
295
    {
296 381
        if ($this->_current_charset == 'utf-8') {
297 381
            return $string;
298
        }
299
        return $this->iconv('utf-8', $this->_current_charset, $string);
300
    }
301
302
    /**
303
     * Converts the given string to the current site charset.
304
     *
305
     * @return string The converted string.
306
     */
307
    public function convert_to_current_charset(string $string)
308
    {
309
        $charset = mb_detect_encoding($string, "UTF-8, UTF-7, ASCII, ISO-8859-15");
310
        debug_add("mb_detect_encoding got {$charset}");
311
        return $this->iconv($charset, $this->_current_charset, $string);
312
    }
313
314
    /**
315
     * Wrapped html_entity_decode call
316
     */
317 14
    public function html_entity_decode(string $text) : string
318
    {
319 14
        return html_entity_decode($text, ENT_COMPAT, $this->_current_charset);
320
    }
321
}
322