Issues (17)

src/LangHelper.php (4 issues)

Labels
1
<?php
2
3
namespace LeKoala\Multilingual;
4
5
use Exception;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\i18n\i18n;
8
use SilverStripe\Control\Cookie;
9
use SilverStripe\Control\Session;
10
use SilverStripe\Control\Director;
11
use TractorCow\Fluent\Model\Locale;
0 ignored issues
show
The type TractorCow\Fluent\Model\Locale was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use TractorCow\Fluent\State\FluentState;
0 ignored issues
show
The type TractorCow\Fluent\State\FluentState was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use SilverStripe\Core\Config\Configurable;
14
15
/**
16
 * i18n helper class
17
 */
18
class LangHelper
19
{
20
    use Configurable;
21
22
    /**
23
     * The default key for global translation
24
     */
25
    const GLOBAL_ENTITY = 'Global';
26
27
    /**
28
     * Provision fluent locales defined in yml
29
     * Use LangHelper::provisionLocales
30
     *
31
     * eg:
32
     * LeKoala\Multilingual\LangHelper:
33
     *   default_locales:
34
     *     - en_US
35
     *     - fr_FR
36
     *
37
     * @var array<string>
38
     */
39
    private static $default_locales = [];
40
41
    /**
42
     * @config
43
     * @var boolean
44
     */
45
    private static $persist_cookie = true;
46
47
    /**
48
     * @var array<string,string>
49
     */
50
    protected static $locale_cache = [];
51
52
    /**
53
     * Get a global translation
54
     *
55
     * By default all global translation are stored under the Global key
56
     *
57
     * @param string $entity If no entity is specified, Global is assumed
58
     * @return string
59
     */
60
    public static function globalTranslation(string $entity): string
61
    {
62
        $parts = explode('.', $entity);
63
        if (count($parts) == 1) {
64
            array_unshift($parts, self::GLOBAL_ENTITY);
65
        }
66
        return i18n::_t(implode('.', $parts), $entity);
67
    }
68
69
    public static function hasFluent(): bool
70
    {
71
        return class_exists('\\TractorCow\\Fluent\\Middleware\\DetectLocaleMiddleware');
72
    }
73
74
    /**
75
     * Call this to make sure we are not setting any cookies that has
76
     * not been accepted
77
     */
78
    public static function persistLocaleIfCookiesAreAllowed(): void
79
    {
80
        if (headers_sent()) {
81
            return;
82
        }
83
84
        if (!self::hasFluent()) {
85
            return;
86
        }
87
88
        $persist = static::config()->persist_cookie;
89
        if (!$persist) {
90
            return;
91
        }
92
93
        // If we choose to persist cookies, we should check our cookie consent first
94
        $dont_persist = $persist;
95
        // cookie consent from osano
96
        $status = Cookie::get('cookieconsent_status') ?? '';
97
        if (strlen($status) && $status == 'allow') {
98
            $dont_persist = false;
99
        }
100
        // cookie from cookieconsent
101
        $status = Cookie::get('cookie_consent_user_accepted') ?? '';
102
        if (strlen($status) && $status == 'true') {
103
            $dont_persist = false;
104
        }
105
106
        if ($dont_persist) {
107
            return;
108
        }
109
110
        self::persistLocale();
111
    }
112
113
    public static function getLanguageName($locale, $short = true)
114
    {
115
        $locale = self::get_locale_from_lang($locale);
116
117
        $allLocales = i18n::getSources()->getKnownLocales();
118
119
        $foundLocale = $allLocales[$locale] ?? null;
120
        if ($foundLocale && $short) {
121
            $foundLocale = preg_replace('/\s*\(.*?\)/', '', $foundLocale);
122
            $foundLocale = ucfirst(trim($foundLocale));
123
        }
124
        return $foundLocale;
125
    }
126
127
    /**
128
     * Persist locale according to fluent settings
129
     */
130
    public static function persistLocale(): void
131
    {
132
        if (!self::hasFluent()) {
133
            return;
134
        }
135
136
        $curr = Controller::curr();
137
        $request = $curr->getRequest();
138
139
        $secure = Director::is_https($request)
140
            && Session::config()->get('cookie_secure');
141
142
        $class = \TractorCow\Fluent\Middleware\DetectLocaleMiddleware::class;
0 ignored issues
show
The type TractorCow\Fluent\Middle...\DetectLocaleMiddleware was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
143
        $persistIds = $class::config()->get('persist_ids');
144
        $persistKey = FluentState::singleton()->getIsFrontend()
145
            ? $persistIds['frontend']
146
            : $persistIds['cms'];
147
148
        $locale = $request->getSession()->get($persistKey);
149
        // If session is not started or set, it may not be set
150
        if (!$locale) {
151
            $locale = self::get_locale();
152
        }
153
154
        Cookie::set(
155
            $persistKey,
156
            $locale,
157
            $class::config()->get('persist_cookie_expiry'),
158
            $class::config()->get('persist_cookie_path'),
159
            $class::config()->get('persist_cookie_domain'),
160
            $secure,
161
            $class::config()->get('persist_cookie_http_only')
162
        );
163
    }
164
165
    /**
166
     * Provision locales defined in default_locales
167
     *
168
     * @return void
169
     */
170
    public static function provisionLocales()
171
    {
172
        $locales = self::config()->default_locales;
173
        if (empty($locales)) {
174
            throw new Exception("No locales defined in LangHelper:default_locales");
175
        }
176
177
        foreach ($locales as $loc) {
178
            $Locale = Locale::get()->filter('Locale', $loc)->first();
179
            $allLocales = i18n::getData()->getLocales();
180
            if (!$Locale) {
181
                $Locale = new Locale();
182
                $Locale->Title = $allLocales[$loc];
183
                $Locale->Locale = $loc;
184
                $Locale->URLSegment = self::get_lang($loc);
185
                $Locale->IsGlobalDefault = $loc == i18n::get_locale();
186
                $Locale->write();
187
            }
188
        }
189
    }
190
191
    /**
192
     * Make sure we get a proper two characters lang
193
     *
194
     * @param string|object $lang a string or a fluent locale object
195
     * @return string a two chars lang
196
     */
197
    public static function get_lang($lang = null)
198
    {
199
        if (!$lang) {
200
            $lang = self::get_locale();
201
        }
202
        if (is_object($lang)) {
203
            $lang = $lang->Locale;
204
        }
205
        return substr($lang, 0, 2);
206
    }
207
208
    /**
209
     * Get the right locale (using fluent data if exists)
210
     *
211
     * @return string
212
     */
213
    public static function get_locale()
214
    {
215
        if (class_exists(FluentState::class)) {
216
            $locale = FluentState::singleton()->getLocale();
217
            // Locale may not be set, in tests for instance
218
            if ($locale) {
219
                return $locale;
220
            }
221
        }
222
        return i18n::get_locale();
223
    }
224
225
    /**
226
     * Get a locale from the lang
227
     *
228
     * @param string $lang
229
     * @return string
230
     */
231
    public static function get_locale_from_lang($lang)
232
    {
233
        // Use cache
234
        if (isset(self::$locale_cache[$lang])) {
235
            return self::$locale_cache[$lang];
236
        }
237
        // Use fluent data
238
        if (class_exists(Locale::class)) {
239
            if (empty(self::$locale_cache)) {
240
                $fluentLocales = Locale::getLocales();
241
                foreach ($fluentLocales as $locale) {
242
                    self::$locale_cache[self::get_lang($locale->Locale)] = $locale->Locale;
243
                }
244
            }
245
        }
246
        // Use i18n data
247
        if (!isset(self::$locale_cache[$lang])) {
248
            $localesData = i18n::getData();
249
            self::$locale_cache[$lang] = $localesData->localeFromLang($lang);
250
        }
251
        // Return cached value
252
        return self::$locale_cache[$lang];
253
    }
254
255
    /**
256
     * Do we have the subsite module installed
257
     * TODO: check if it might be better to use module manifest instead?
258
     *
259
     * @return bool
260
     */
261
    public static function usesFluent()
262
    {
263
        return class_exists(FluentState::class);
264
    }
265
266
    /**
267
     * @return array<string>
268
     */
269
    public static function get_available_langs()
270
    {
271
        if (!self::usesFluent()) {
272
            return [
273
                self::get_lang()
274
            ];
275
        }
276
277
        $allLocales = Locale::get();
278
        $results = [];
279
        foreach ($allLocales as $locale) {
280
            $results[] = $locale->URLSegment;
281
        }
282
        return $results;
283
    }
284
285
    /**
286
     * Execute the callback in given subsite
287
     *
288
     * @param string $locale
289
     * @param callable $cb
290
     * @return mixed the callback result
291
     */
292
    public static function withLocale($locale, $cb)
293
    {
294
        if (!self::usesFluent() || !$locale) {
295
            $cb();
296
            return;
297
        }
298
        if (!is_string($locale)) {
0 ignored issues
show
The condition is_string($locale) is always true.
Loading history...
299
            $locale = $locale->Locale;
300
        }
301
        $state = FluentState::singleton();
302
        // @link https://github.com/tractorcow-farm/silverstripe-fluent/issues/327
303
        $currentLocale = i18n::get_locale();
304
        i18n::set_locale(FluentState::singleton()->getLocale());
305
        $result = $state->withState(function ($state) use ($locale, $cb) {
306
            $state->setLocale($locale);
307
            return $cb();
308
        });
309
        i18n::set_locale($currentLocale);
310
        return $result;
311
    }
312
313
    /**
314
     * Execute the callback for all locales
315
     *
316
     * @param callable $cb
317
     * @return array an array of callback results
318
     */
319
    public static function withLocales($cb)
320
    {
321
        if (!self::usesFluent()) {
322
            $cb();
323
            return [];
324
        }
325
        $allLocales = Locale::get();
326
        $results = [];
327
        foreach ($allLocales as $locale) {
328
            $results[] = self::withLocale($locale, $cb);
329
        }
330
        return $results;
331
    }
332
333
    /**
334
     * Convert a locale title to a simple lang name
335
     * @param string $locale
336
     * @return string
337
     */
338
    public static function localeToLang(string $locale): string
339
    {
340
        // Remove parenthesis
341
        $cleanedLocale = preg_replace('/\s*\(.*?\)/', '', $locale);
342
        // First letter should be uppercased for consistency
343
        $cleanedLocale = mb_ucfirst(trim($cleanedLocale));
344
        return $cleanedLocale;
345
    }
346
347
    /**
348
     * Get all fluent locales
349
     * @return array
350
     */
351
    public static function getApplicationLanguages(): array
352
    {
353
        if (!self::usesFluent()) {
354
            $title = i18n::getData()->languageName(i18n::get_locale());
355
            return [
356
                i18n::get_locale() => self::localeToLang($title)
357
            ];
358
        }
359
        $locales = Locale::get();
360
        $arr = [];
361
        foreach ($locales as $loc) {
362
            $arr[$loc->Locale] = self::localeToLang($loc->getTitle());
363
        }
364
        return $arr;
365
    }
366
}
367