Passed
Push — master ( 07213d...3b1a54 )
by Andreas
11:05
created

midcom_services_i18n::set_fallback_language()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 6
rs 10
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
        try {
157 3
            Locales::getName($lang);
158
        } catch (Exception $e) {
159
            debug_add("Language {$lang} not found:" . $e->getMessage(), MIDCOM_LOG_ERROR);
160
            return false;
161
        }
162
163 3
        $this->_current_language = $lang;
164
165 3
        setlocale(LC_ALL, $lang);
166 3
        if (Intl::isExtensionLoaded()) {
167 3
            Locale::setDefault($lang);
168
        }
169
170 3
        foreach ($this->_obj_l10n as $object) {
171
            $object->set_language($lang);
172
        }
173 3
        return true;
174
    }
175
176
    /**
177
     * Set the fallback language.
178
     */
179
    public function set_fallback_language(string $lang)
180
    {
181
        $this->_fallback_language = $lang;
182
        foreach ($this->_obj_l10n as $object) {
183
            $object->set_fallback_language($lang);
184
        }
185
    }
186
187
    /**
188
     * Returns the current language code
189
     */
190 398
    public function get_current_language() : string
191
    {
192 398
        return $this->_current_language;
193
    }
194
195
    /**
196
     * Returns the current fallback language code
197
     */
198 51
    public function get_fallback_language() : string
199
    {
200 51
        return $this->_fallback_language;
201
    }
202
203
    /**
204
     * Returns the current character set
205
     */
206 108
    public function get_current_charset() : string
207
    {
208 108
        return $this->_current_charset;
209
    }
210
211
    /**
212
     * Returns a l10n class instance which can be used to
213
     * access the localization data of the current component.
214
     *
215
     * If loading failed, midcom_error is thrown, otherwise the l10n
216
     * db cache is populated accordingly.
217
     *
218
     * @see midcom_services_i18n_l10n
219
     */
220 406
    public function get_l10n(string $component = 'midcom') : midcom_services_i18n_l10n
221
    {
222 406
        if (!array_key_exists($component, $this->_obj_l10n)) {
223 5
            $this->_obj_l10n[$component] = new midcom_services_i18n_l10n($component, $this->_current_language, $this->_fallback_language);
224
        }
225
226 406
        return $this->_obj_l10n[$component];
227
    }
228
229
    /**
230
     * Returns a translated string using the l10n database specified in the function
231
     * arguments.
232
     *
233
     * @see midcom_services_i18n_l10n::get()
234
     */
235 370
    public function get_string(string $stringid, string $component) : string
236
    {
237
        try {
238 370
            return $this->get_l10n($component)->get($stringid);
239
        } catch (midcom_error $e) {
240
            $e->log(MIDCOM_LOG_WARN);
241
            return $stringid;
242
        }
243
    }
244
245
    /**
246
     * This is a shortcut for echo $this->get_string(...);.
247
     *
248
     * To keep the naming stable with the actual l10n class, this is not called
249
     * echo_string (Zend won't allow $l10n->echo().)
250
     *
251
     * @see midcom_services_i18n_l10n::get()
252
     * @see get_string()
253
     */
254
    public function show_string(string $stringid, string $component)
255
    {
256
        echo $this->get_string($stringid, $component);
257
    }
258
259
    /**
260
     * This is a calling wrapper to the iconv library.
261
     *
262
     * See the PHP iconv() function for the exact parameter definitions.
263
     *
264
     * @return mixed The converted string or false on any error.
265
     */
266
    private function iconv(string $source_charset, string $destination_charset, string $string)
267
    {
268
        $result = @iconv($source_charset, $destination_charset, $string);
269
        if ($result === false && !empty($string)) {
270
            debug_add("Iconv returned failed to convert a string, returning an empty string.", MIDCOM_LOG_WARN);
271
            debug_print_r("Tried to convert this string from {$source_charset} to {$destination_charset}:", $string);
272
            midcom::get()->debug->log_php_error(MIDCOM_LOG_WARN);
273
            return false;
274
        }
275
        return $result;
276
    }
277
278
    /**
279
     * Convert a string assumed to be in the currently active charset to UTF8.
280
     *
281
     * @return string The string converted to UTF-8
282
     */
283
    public function convert_to_utf8(string $string)
284
    {
285
        if ($this->_current_charset == 'utf-8') {
286
            return $string;
287
        }
288
        return $this->iconv($this->_current_charset, 'utf-8', $string);
289
    }
290
291
    /**
292
     * Convert a string assumed to be in UTF-8 to the currently active charset.
293
     *
294
     * @return string The string converted to the current charset
295
     */
296 381
    public function convert_from_utf8(string $string)
297
    {
298 381
        if ($this->_current_charset == 'utf-8') {
299 381
            return $string;
300
        }
301
        return $this->iconv('utf-8', $this->_current_charset, $string);
302
    }
303
304
    /**
305
     * Converts the given string to the current site charset.
306
     *
307
     * @return string The converted string.
308
     */
309
    public function convert_to_current_charset(string $string)
310
    {
311
        $charset = mb_detect_encoding($string, "UTF-8, UTF-7, ASCII, ISO-8859-15");
312
        debug_add("mb_detect_encoding got {$charset}");
313
        return $this->iconv($charset, $this->_current_charset, $string);
314
    }
315
316
    /**
317
     * Wrapped html_entity_decode call
318
     */
319 14
    public function html_entity_decode(string $text) : string
320
    {
321 14
        return html_entity_decode($text, ENT_COMPAT, $this->_current_charset);
322
    }
323
}
324