cutelyst 5.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
langselect.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2018-2022 Matthias Fehring <mf@huessenbergnetz.de>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5
6#include "langselect_p.h"
7
8#include <Cutelyst/Application>
9#include <Cutelyst/Context>
10#include <Cutelyst/Engine>
11#include <Cutelyst/Plugins/Session/Session>
12#include <Cutelyst/Response>
13#include <Cutelyst/utils.h>
14#include <map>
15#include <utility>
16
17#include <QDir>
18#include <QFileInfo>
19#include <QLoggingCategory>
20#include <QUrl>
21#include <QUrlQuery>
22
23Q_LOGGING_CATEGORY(C_LANGSELECT, "cutelyst.plugin.langselect", QtWarningMsg)
24
25using namespace Cutelyst;
26using namespace Qt::Literals::StringLiterals;
27
28const QString LangSelectPrivate::stashKeySelectionTried{u"_c_langselect_tried"_s};
29
30// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
31namespace {
32thread_local LangSelect *lsp = nullptr;
33} // namespace
34
36 : Plugin(parent)
37 , d_ptr(new LangSelectPrivate)
38{
39 Q_D(LangSelect);
40 d->source = source;
41 d->autoDetect = true;
42}
43
45 : Plugin(parent)
46 , d_ptr(new LangSelectPrivate)
47{
48 Q_D(LangSelect);
49 d->source = AcceptHeader;
50 d->autoDetect = false;
51}
52
53LangSelect::~LangSelect() = default;
54
56{
57 Q_D(LangSelect);
58
59 const QVariantMap config = app->engine()->config(u"Cutelyst_LangSelect_Plugin"_s);
60
61 bool cookieExpirationOk = false;
62 const QString cookieExpireStr =
63 config.value(u"cookie_expiration"_s, static_cast<qint64>(d->cookieExpiration.count()))
64 .toString();
65 d->cookieExpiration = std::chrono::duration_cast<std::chrono::seconds>(
66 Utils::durationFromString(cookieExpireStr, &cookieExpirationOk));
67 if (!cookieExpirationOk) {
68 qCWarning(C_LANGSELECT).nospace() << "Invalid value set for cookie_expiration. "
69 "Using default value "
70#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
71 << LangSelectPrivate::cookieDefaultExpiration;
72#else
73 << "1 month";
74#endif
75 d->cookieExpiration = LangSelectPrivate::cookieDefaultExpiration;
76 }
77
78 d->cookieDomain = config.value(u"cookie_domain"_s).toString();
79
80 const QString _sameSite = config.value(u"cookie_same_site"_s, u"lax"_s).toString();
81 if (_sameSite.compare(u"default", Qt::CaseInsensitive) == 0) {
82 d->cookieSameSite = QNetworkCookie::SameSite::Default;
83 } else if (_sameSite.compare(u"none", Qt::CaseInsensitive) == 0) {
84 d->cookieSameSite = QNetworkCookie::SameSite::None;
85 } else if (_sameSite.compare(u"stric", Qt::CaseInsensitive) == 0) {
86 d->cookieSameSite = QNetworkCookie::SameSite::Strict;
87 } else if (_sameSite.compare(u"lax", Qt::CaseInsensitive) == 0) {
88 d->cookieSameSite = QNetworkCookie::SameSite::Lax;
89 } else {
90 qCWarning(C_LANGSELECT).nospace() << "Invalid value set for cookie_same_site. "
91 "Using default value "
92 << QNetworkCookie::SameSite::Lax;
93 d->cookieSameSite = QNetworkCookie::SameSite::Lax;
94 }
95
96 d->cookieSecure = config.value(u"cookie_secure"_s).toBool();
97
98 if ((d->cookieSameSite == QNetworkCookie::SameSite::None) && !d->cookieSecure) {
99 qCWarning(C_LANGSELECT) << "cookie_same_site has been set to None but cookie_secure is "
100 "not set to true. Implicitely setting cookie_secure to true. "
101 "Please check your configuration.";
102 d->cookieSecure = true;
103 }
104
105 if (d->fallbackLocale.language() == QLocale::C) {
106 qCCritical(C_LANGSELECT) << "We need a valid fallback locale.";
107 return false;
108 }
109 if (d->autoDetect) {
110 if (d->source < Fallback) {
111 if (d->source == URLQuery && d->queryKey.isEmpty()) {
112 qCCritical(C_LANGSELECT) << "Can not use url query as source with empty key name.";
113 return false;
114 } else if (d->source == Session && d->sessionKey.isEmpty()) {
115 qCCritical(C_LANGSELECT) << "Can not use session as source with empty key name.";
116 return false;
117 } else if (d->source == Cookie && d->cookieName.isEmpty()) {
118 qCCritical(C_LANGSELECT) << "Can not use cookie as source with empty cookie name.";
119 return false;
120 }
121 } else {
122 qCCritical(C_LANGSELECT) << "Invalid source.";
123 return false;
124 }
125 connect(app, &Application::beforePrepareAction, this, [d](Context *c, bool *skipMethod) {
126 d->beforePrepareAction(c, skipMethod);
127 });
128 }
129 if (!d->locales.contains(d->fallbackLocale)) {
130 d->locales.append(d->fallbackLocale);
131 }
132 connect(app, &Application::postForked, this, &LangSelectPrivate::_q_postFork);
133
134 qCDebug(C_LANGSELECT) << "Initialized LangSelect plugin with the following settings:";
135 qCDebug(C_LANGSELECT) << "Supported locales:" << d->locales;
136 qCDebug(C_LANGSELECT) << "Fallback locale:" << d->fallbackLocale;
137 qCDebug(C_LANGSELECT) << "Auto detection source:" << d->source;
138 qCDebug(C_LANGSELECT) << "Detect from header:" << d->detectFromHeader;
139
140 return true;
141}
142
144{
145 Q_D(LangSelect);
146 d->locales.clear();
147 d->locales.reserve(locales.size());
148 for (const QLocale &l : locales) {
149 if (Q_LIKELY(l.language() != QLocale::C)) {
150 d->locales.push_back(l);
151 } else {
152 qCWarning(C_LANGSELECT)
153 << "Can not add invalid locale" << l << "to the list of supported locales.";
154 }
155 }
156}
157
159{
160 Q_D(LangSelect);
161 d->locales.clear();
162 d->locales.reserve(locales.size());
163 for (const QString &l : locales) {
164 QLocale locale(l);
165 if (Q_LIKELY(locale.language() != QLocale::C)) {
166 d->locales.push_back(locale);
167 } else {
168 qCWarning(C_LANGSELECT)
169 << "Can not add invalid locale" << l << "to the list of supported locales.";
170 }
171 }
172}
173
175{
176 if (Q_LIKELY(locale.language() != QLocale::C)) {
177 Q_D(LangSelect);
178 d->locales.push_back(locale);
179 } else {
180 qCWarning(C_LANGSELECT) << "Can not add invalid locale" << locale
181 << "to the list of supported locales.";
182 }
183}
184
186{
187 QLocale l(locale);
188 if (Q_LIKELY(l.language() != QLocale::C)) {
189 Q_D(LangSelect);
190 d->locales.push_back(l);
191 } else {
192 qCWarning(C_LANGSELECT) << "Can not add invalid locale" << locale
193 << "to the list of supported locales.";
194 }
195}
196
198 const QString &name,
199 const QString &prefix,
200 const QString &suffix)
201{
202 Q_D(LangSelect);
203 d->locales.clear();
204 if (Q_LIKELY(!path.isEmpty() && !name.isEmpty())) {
205 const QDir dir(path);
206 if (Q_LIKELY(dir.exists())) {
207 const auto _pref = prefix.isEmpty() ? u"."_s : prefix;
208 const auto _suff = suffix.isEmpty() ? u".qm"_s : suffix;
209 const QString filter = name + _pref + u'*' + _suff;
210 const auto files = dir.entryInfoList({name}, QDir::Files);
211 if (Q_LIKELY(!files.empty())) {
212 d->locales.reserve(files.size());
213 bool shrinkToFit = false;
214 for (const QFileInfo &fi : files) {
215 const auto fn = fi.fileName();
216 const auto prefIdx = fn.indexOf(_pref);
217 const auto locPart =
218 fn.mid(prefIdx + _pref.length(),
219 fn.length() - prefIdx - _suff.length() - _pref.length());
220 QLocale l(locPart);
221 if (Q_LIKELY(l.language() != QLocale::C)) {
222 d->locales.push_back(l);
223 qCDebug(C_LANGSELECT)
224 << "Added locale" << locPart << "to the list of supported locales.";
225 } else {
226 shrinkToFit = true;
227 qCWarning(C_LANGSELECT) << "Can not add invalid locale" << locPart
228 << "to the list of supported locales.";
229 }
230 }
231 if (shrinkToFit) {
232 d->locales.squeeze();
233 }
234 } else {
235 qCWarning(C_LANGSELECT)
236 << "Can not find translation files for" << filter << "in" << path;
237 }
238 } else {
239 qCWarning(C_LANGSELECT) << "Can not set locales from not existing directory" << path;
240 }
241 } else {
242 qCWarning(C_LANGSELECT) << "Can not set locales from dir with empty path or name.";
243 }
244}
245
246void LangSelect::setLocalesFromDirs(const QString &path, const QString &name)
247{
248 Q_D(LangSelect);
249 d->locales.clear();
250 if (Q_LIKELY(!path.isEmpty() && !name.isEmpty())) {
251 const QDir dir(path);
252 if (Q_LIKELY(dir.exists())) {
253 const auto dirs = dir.entryList(QDir::AllDirs);
254 if (Q_LIKELY(!dirs.empty())) {
255 d->locales.reserve(dirs.size());
256 bool shrinkToFit = false;
257 for (const QString &subDir : dirs) {
258 const QString relFn = subDir + u'/' + name;
259 if (dir.exists(relFn)) {
260 QLocale l(subDir);
261 if (Q_LIKELY(l.language() != QLocale::C)) {
262 d->locales.push_back(l);
263 qCDebug(C_LANGSELECT)
264 << "Added locale" << subDir << "to the list of supported locales.";
265 } else {
266 shrinkToFit = true;
267 qCWarning(C_LANGSELECT) << "Can not add invalid locale" << subDir
268 << "to the list of supported locales.";
269 }
270 } else {
271 shrinkToFit = true;
272 }
273 }
274 if (shrinkToFit) {
275 d->locales.squeeze();
276 }
277 }
278 } else {
279 qCWarning(C_LANGSELECT) << "Can not set locales from not existing directory" << path;
280 }
281 } else {
282 qCWarning(C_LANGSELECT) << "Can not set locales from dirs with empty path or names.";
283 }
284}
285
287{
288 Q_D(const LangSelect);
289 return d->locales;
290}
291
293{
294 Q_D(LangSelect);
295 d->queryKey = key;
296}
297
299{
300 Q_D(LangSelect);
301 d->sessionKey = key;
302}
303
305{
306 Q_D(LangSelect);
307 d->cookieName = name;
308}
309
311{
312 Q_D(LangSelect);
313 d->subDomainMap.clear();
314 d->locales.clear();
315 d->locales.reserve(map.size());
316 for (const auto &[key, value] : map.asKeyValueRange()) {
317 if (value.language() != QLocale::C) {
318 d->subDomainMap.insert(key, value);
319 d->locales.append(value);
320 } else {
321 qCWarning(C_LANGSELECT) << "Can not add invalid locale" << value << "for subdomain"
322 << key << "to the subdomain map.";
323 }
324 }
325 d->locales.squeeze();
326}
327
329{
330 Q_D(LangSelect);
331 d->domainMap.clear();
332 d->locales.clear();
333 d->locales.reserve(map.size());
334 for (const auto &[key, value] : map.asKeyValueRange()) {
335 if (Q_LIKELY(value.language() != QLocale::C)) {
336 d->domainMap.insert(key, value);
337 d->locales.append(value);
338 } else {
339 qCWarning(C_LANGSELECT) << "Can not add invalid locale" << value << "for domain" << key
340 << "to the domain map.";
341 }
342 }
343 d->locales.squeeze();
344}
345
347{
348 Q_D(LangSelect);
349 d->fallbackLocale = fallback;
350}
351
353{
354 Q_D(LangSelect);
355 d->detectFromHeader = enabled;
356}
357
359{
360 Q_D(LangSelect);
361 if (Q_LIKELY(!key.isEmpty())) {
362 d->langStashKey = key;
363 } else {
364 qCWarning(C_LANGSELECT) << "Can not set an empty key name for the language code stash key. "
365 "Using current key name"
366 << d->langStashKey;
367 }
368}
369
371{
372 Q_D(LangSelect);
373 if (Q_LIKELY(!key.isEmpty())) {
374 d->dirStashKey = key;
375 } else {
376 qCWarning(C_LANGSELECT) << "Can not set an empty key name for the language direction stash "
377 "key. Using current key name"
378 << d->dirStashKey;
379 }
380}
381
383{
384 if (!lsp) {
385 qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
386 return {};
387 }
388
389 return lsp->supportedLocales();
390}
391
393{
394 if (!lsp) {
395 qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
396 return true;
397 }
398
399 const auto d = lsp->d_ptr.get();
400 const auto _key = !key.isEmpty() ? key : d->queryKey;
401 if (!d->getFromQuery(c, _key)) {
402 if (!d->getFromHeader(c)) {
403 d->setFallback(c);
404 }
405 d->setToQuery(c, _key);
406 c->detach();
407 return false;
408 }
409 d->setContentLanguage(c);
410
411 return true;
412}
413
415{
416 bool foundInSession = false;
417
418 if (!lsp) {
419 qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
420 return foundInSession;
421 }
422
423 const auto d = lsp->d_ptr.get();
424 const auto _key = !key.isEmpty() ? key : d->sessionKey;
425 foundInSession = d->getFromSession(c, _key);
426 if (!foundInSession) {
427 if (!d->getFromHeader(c)) {
428 d->setFallback(c);
429 }
430 d->setToSession(c, _key);
431 }
432 d->setContentLanguage(c);
433
434 return foundInSession;
435}
436
438{
439 bool foundInCookie = false;
440
441 if (!lsp) {
442 qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
443 return foundInCookie;
444 }
445
446 const auto d = lsp->d_ptr.get();
447 const auto _name = !name.isEmpty() ? name : d->cookieName;
448 foundInCookie = d->getFromCookie(c, _name);
449 if (!foundInCookie) {
450 if (!d->getFromHeader(c)) {
451 d->setFallback(c);
452 }
453 d->setToCookie(c, _name);
454 }
455 d->setContentLanguage(c);
456
457 return foundInCookie;
458}
459
461{
462 bool foundInSubDomain = false;
463
464 if (!lsp) {
465 qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
466 return foundInSubDomain;
467 }
468
469 const auto d = lsp->d_ptr.get();
470 const auto _map = !subDomainMap.empty() ? subDomainMap : d->subDomainMap;
471 foundInSubDomain = d->getFromSubdomain(c, _map);
472 if (!foundInSubDomain) {
473 if (!d->getFromHeader(c)) {
474 d->setFallback(c);
475 }
476 }
477
478 d->setContentLanguage(c);
479
480 return foundInSubDomain;
481}
482
484{
485 bool foundInDomain = false;
486
487 if (!lsp) {
488 qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
489 return foundInDomain;
490 }
491
492 const auto d = lsp->d_ptr.get();
493 const auto _map = !domainMap.empty() ? domainMap : d->domainMap;
494 foundInDomain = d->getFromDomain(c, _map);
495 if (!foundInDomain) {
496 if (!d->getFromHeader(c)) {
497 d->setFallback(c);
498 }
499 }
500
501 d->setContentLanguage(c);
502
503 return foundInDomain;
504}
505
506bool LangSelect::fromPath(Context *c, const QString &locale)
507{
508 if (!lsp) {
509 qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
510 return true;
511 }
512
513 const auto d = lsp->d_ptr.get();
514 const QLocale l(locale);
515 if (l.language() != QLocale::C && d->locales.contains(l)) {
516 qCDebug(C_LANGSELECT) << "Found valid locale" << l << "in path";
517 c->setLocale(l);
518 d->setContentLanguage(c);
519 return true;
520 } else {
521 if (!d->getFromHeader(c)) {
522 d->setFallback(c);
523 }
524 auto uri = c->req()->uri();
525 auto pathParts = uri.path().split(u'/');
526 const auto localeIdx = pathParts.indexOf(locale);
527 pathParts[localeIdx] = c->locale().bcp47Name().toLower();
528 uri.setPath(pathParts.join(u'/'));
529 qCDebug(C_LANGSELECT) << "Storing selected locale by redirecting to" << uri;
530 c->res()->redirect(uri, Response::TemporaryRedirect);
531 c->detach();
532 return false;
533 }
534}
535
536bool LangSelectPrivate::detectLocale(Context *c, LangSelect::Source _source, bool *skipMethod) const
537{
538 bool redirect = false;
539
541
542 if (_source == LangSelect::Session) {
543 if (getFromSession(c, sessionKey)) {
544 foundIn = _source;
545 }
546 } else if (_source == LangSelect::Cookie) {
547 if (getFromCookie(c, cookieName)) {
548 foundIn = _source;
549 }
550 } else if (_source == LangSelect::URLQuery) {
551 if (getFromQuery(c, queryKey)) {
552 foundIn = _source;
553 }
554 } else if (_source == LangSelect::SubDomain) {
555 if (getFromSubdomain(c, subDomainMap)) {
556 foundIn = _source;
557 }
558 } else if (_source == LangSelect::Domain) {
559 if (getFromDomain(c, domainMap)) {
560 foundIn = _source;
561 }
562 }
563
564 // could not find supported locale in specified source
565 // falling back to Accept-Language header
566 if (foundIn == LangSelect::Fallback && getFromHeader(c)) {
567 foundIn = LangSelect::AcceptHeader;
568 }
569
570 if (foundIn == LangSelect::Fallback) {
571 setFallback(c);
572 }
573
574 if (foundIn != _source) {
575 if (_source == LangSelect::Session) {
576 setToSession(c, sessionKey);
577 } else if (_source == LangSelect::Cookie) {
578 setToCookie(c, cookieName);
579 } else if (_source == LangSelect::URLQuery) {
580 setToQuery(c, queryKey);
581 redirect = true;
582 if (skipMethod) {
583 *skipMethod = true;
584 }
585 }
586 }
587
588 if (!redirect) {
589 setContentLanguage(c);
590 }
591
592 return redirect;
593}
594
595bool LangSelectPrivate::getFromQuery(Context *c, const QString &key) const
596{
597 const QLocale l(c->req()->queryParam(key));
598 if (l.language() != QLocale::C && locales.contains(l)) {
599 qCDebug(C_LANGSELECT) << "Found valid locale" << l << "in url query key" << key;
600 c->setLocale(l);
601 return true;
602 } else {
603 qCDebug(C_LANGSELECT) << "Can not find supported locale in url query key" << key;
604 return false;
605 }
606}
607
608bool LangSelectPrivate::getFromCookie(Context *c, const QByteArray &cookie) const
609{
610 const QLocale l(QString::fromLatin1(c->req()->cookie(cookie)));
611 if (l.language() != QLocale::C && locales.contains(l)) {
612 qCDebug(C_LANGSELECT) << "Found valid locale" << l << "in cookie name" << cookie;
613 c->setLocale(l);
614 return true;
615 } else {
616 qCDebug(C_LANGSELECT) << "Can no find supported locale in cookie value with name" << cookie;
617 return false;
618 }
619}
620
621bool LangSelectPrivate::getFromSession(Context *c, const QString &key) const
622{
623 const QLocale l = Cutelyst::Session::value(c, key).toLocale();
624 if (l.language() != QLocale::C && locales.contains(l)) {
625 qCDebug(C_LANGSELECT) << "Found valid locale" << l << "in session key" << key;
626 c->setLocale(l);
627 return true;
628 } else {
629 qCDebug(C_LANGSELECT) << "Can not find supported locale in session value with key" << key;
630 return false;
631 }
632}
633
634bool LangSelectPrivate::getFromSubdomain(Context *c, const QMap<QString, QLocale> &map) const
635{
636 const auto domain = c->req()->uri().host();
637 for (const auto &[key, value] : map.asKeyValueRange()) {
638 if (domain.startsWith(key)) {
639 qCDebug(C_LANGSELECT) << "Found valid locale" << value << "in subdomain map for domain"
640 << domain;
641 c->setLocale(value);
642 return true;
643 }
644 }
645
646 const auto domainParts = domain.split(u'.', Qt::SkipEmptyParts);
647 if (domainParts.size() > 2) {
648 const QLocale l(domainParts.at(0));
649 if (l.language() != QLocale::C && locales.contains(l)) {
650 qCDebug(C_LANGSELECT) << "Found supported locale" << l << "in subdomain of domain"
651 << domain;
652 c->setLocale(l);
653 return true;
654 }
655 }
656 qCDebug(C_LANGSELECT) << "Can not find supported locale for subdomain" << domain;
657 return false;
658}
659
660bool LangSelectPrivate::getFromDomain(Context *c, const QMap<QString, QLocale> &map) const
661{
662 const auto domain = c->req()->uri().host();
663 for (const auto &[key, value] : map.asKeyValueRange()) {
664 if (domain.endsWith(key)) {
665 qCDebug(C_LANGSELECT) << "Found valid locale" << value << "in domain map for domain"
666 << domain;
667 c->setLocale(value);
668 return true;
669 }
670 }
671
672 const auto domainParts = domain.split(u'.', Qt::SkipEmptyParts);
673 if (domainParts.size() > 1) {
674 const QLocale l(domainParts.at(domainParts.size() - 1));
675 if (l.language() != QLocale::C && locales.contains(l)) {
676 qCDebug(C_LANGSELECT) << "Found supported locale" << l << "in domain" << domain;
677 c->setLocale(l);
678 return true;
679 }
680 }
681 qCDebug(C_LANGSELECT) << "Can not find supported locale for domain" << domain;
682 return false;
683}
684
685bool LangSelectPrivate::getFromHeader(Context *c, const QByteArray &name) const
686{
687 if (detectFromHeader) {
688 const auto accpetedLangs =
690 if (Q_LIKELY(!accpetedLangs.empty())) {
691 std::map<float, QLocale> langMap;
692 for (const auto &al : accpetedLangs) {
693 const auto idx = al.indexOf(u';');
694 float priority = 1.0F;
695 QString langPart;
696 bool ok = true;
697 if (idx > -1) {
698 langPart = al.left(idx);
699 const auto ref = QStringView(al).mid(idx + 1);
700 priority = ref.mid(ref.indexOf(u'=') + 1).toFloat(&ok);
701 } else {
702 langPart = al;
703 }
704 QLocale locale(langPart);
705 if (ok && locale.language() != QLocale::C) {
706 const auto search = langMap.find(priority);
707 if (search == langMap.cend()) {
708 langMap.insert({priority, locale});
709 }
710 }
711 }
712 if (!langMap.empty()) {
713 auto i = langMap.crbegin();
714 while (i != langMap.crend()) {
715 if (locales.contains(i->second)) {
716 c->setLocale(i->second);
717 qCDebug(C_LANGSELECT)
718 << "Selected locale" << c->locale() << "from" << name << "header";
719 return true;
720 }
721 ++i;
722 }
723 // if there is no exact match, lets try to find a locale
724 // where at least the language matches
725 i = langMap.crbegin();
726 const auto constLocales = locales;
727 while (i != langMap.crend()) {
728 for (const QLocale &l : constLocales) {
729 if (l.language() == i->second.language()) {
730 c->setLocale(l);
731 qCDebug(C_LANGSELECT)
732 << "Selected locale" << c->locale() << "from" << name << "header";
733 return true;
734 }
735 }
736 ++i;
737 }
738 }
739 }
740 }
741
742 return false;
743}
744
745void LangSelectPrivate::setToQuery(Context *c, const QString &key) const
746{
747 auto uri = c->req()->uri();
748 QUrlQuery query(uri);
749 if (query.hasQueryItem(key)) {
750 query.removeQueryItem(key);
751 }
752 query.addQueryItem(key, c->locale().bcp47Name().toLower());
753 uri.setQuery(query);
754 qCDebug(C_LANGSELECT) << "Storing selected" << c->locale() << "in URL query by redirecting to"
755 << uri;
756 c->res()->redirect(uri, Response::TemporaryRedirect);
757}
758
759void LangSelectPrivate::setToCookie(Context *c, const QByteArray &name) const
760{
761 qCDebug(C_LANGSELECT) << "Storing selected" << c->locale() << "in cookie with name" << name;
762 QNetworkCookie cookie(name, c->locale().bcp47Name().toLatin1());
763 cookie.setSameSitePolicy(QNetworkCookie::SameSite::Lax);
764 if (cookieExpiration.count() == 0) {
765 cookie.setExpirationDate(QDateTime());
766 } else {
767 cookie.setExpirationDate(QDateTime::currentDateTime().addDuration(cookieExpiration));
768 }
769 cookie.setDomain(cookieDomain);
770 cookie.setSecure(cookieSecure);
771 cookie.setSameSitePolicy(cookieSameSite);
772 c->res()->setCookie(cookie);
773}
774
775void LangSelectPrivate::setToSession(Context *c, const QString &key) const
776{
777 qCDebug(C_LANGSELECT) << "Storing selected" << c->locale() << "in session key" << key;
778 Session::setValue(c, key, c->locale());
779}
780
781void LangSelectPrivate::setFallback(Context *c) const
782{
783 qCDebug(C_LANGSELECT) << "Can not find fitting locale, using fallback locale" << fallbackLocale;
784 c->setLocale(fallbackLocale);
785}
786
787void LangSelectPrivate::setContentLanguage(Context *c) const
788{
789 if (addContentLanguageHeader) {
790 c->res()->setHeader("Content-Language"_ba, c->locale().bcp47Name().toLatin1());
791 }
792 c->stash(
793 {{langStashKey, c->locale().bcp47Name()},
794 {dirStashKey, (c->locale().textDirection() == Qt::LeftToRight ? u"ltr"_s : u"rtl"_s)}});
795}
796
797void LangSelectPrivate::beforePrepareAction(Context *c, bool *skipMethod) const
798{
799 if (*skipMethod) {
800 return;
801 }
802
803 if (!c->stash(LangSelectPrivate::stashKeySelectionTried).isNull()) {
804 return;
805 }
806
807 detectLocale(c, source, skipMethod);
808
809 c->setStash(LangSelectPrivate::stashKeySelectionTried, true);
810}
811
812void LangSelectPrivate::_q_postFork(Application *app)
813{
814 lsp = app->plugin<LangSelect *>();
815}
816
817#include "moc_langselect.cpp"
The Cutelyst application.
Definition application.h:66
Engine * engine() const noexcept
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
void postForked(Cutelyst::Application *app)
The Cutelyst Context.
Definition context.h:42
void stash(const QVariantHash &unite)
Definition context.cpp:563
void detach(Action *action=nullptr)
Definition context.cpp:340
QLocale locale() const noexcept
Definition context.cpp:461
Response * res() const noexcept
Definition context.cpp:104
void setStash(const QString &key, const QVariant &value)
Definition context.cpp:213
Request * req
Definition context.h:66
void setLocale(const QLocale &locale)
Definition context.cpp:467
QVariantMap config(const QString &entity) const
Definition engine.cpp:122
QString headerAsString(QAnyStringView key) const
Definition headers.cpp:401
Detect and select locale based on different input parameters.
Definition langselect.h:350
void setLocalesFromDir(const QString &path, const QString &name, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
void setDetectFromHeader(bool enabled)
void setLanguageDirStashKey(const QString &key=QStringLiteral("c_langselect_dir"))
void setCookieName(const QByteArray &name)
static bool fromUrlQuery(Context *c, const QString &key={})
static bool fromPath(Context *c, const QString &locale)
static QVector< QLocale > getSupportedLocales()
void setFallbackLocale(const QLocale &fallback)
void setQueryKey(const QString &key)
void setSubDomainMap(const QMap< QString, QLocale > &map)
static bool fromDomain(Context *c, const QMap< QString, QLocale > &domainMap=QMap< QString, QLocale >())
void setDomainMap(const QMap< QString, QLocale > &map)
void setLanguageCodeStashKey(const QString &key=QStringLiteral("c_langselect_lang"))
void setLocalesFromDirs(const QString &path, const QString &name)
static bool fromSubDomain(Context *c, const QMap< QString, QLocale > &subDomainMap=QMap< QString, QLocale >())
QVector< QLocale > supportedLocales() const
~LangSelect() override
static bool fromCookie(Context *c, const QByteArray &name={})
void setSessionKey(const QString &key)
void addSupportedLocale(const QLocale &locale)
bool setup(Application *app) override
static bool fromSession(Context *c, const QString &key={})
void setSupportedLocales(const QVector< QLocale > &locales)
LangSelect(Application *parent, Source source)
Base class for Cutelyst Plugins.
Definition plugin.h:25
Headers headers() const noexcept
Definition request.cpp:313
QString queryParam(const QString &key, const QString &defaultValue={}) const
Definition request.h:591
QByteArray cookie(QAnyStringView name) const
Definition request.cpp:278
void redirect(const QUrl &url, quint16 status=Found)
Definition response.cpp:222
void setHeader(const QByteArray &key, const QByteArray &value)
void setCookie(const QNetworkCookie &cookie)
Definition response.cpp:202
Plugin providing methods for session management.
Definition session.h:161
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
Definition session.cpp:171
static void setValue(Context *c, const QString &key, const QVariant &value)
Definition session.cpp:186
CUTELYST_EXPORT std::chrono::microseconds durationFromString(QStringView str, bool *ok=nullptr)
Definition utils.cpp:302
The Cutelyst namespace holds all public Cutelyst API.
QByteArray::const_reverse_iterator crbegin() const const
bool isEmpty() const const
QDateTime currentDateTime()
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
bool exists() const const
void reserve(qsizetype size)
qsizetype size() const const
QString bcp47Name() const const
QLocale::Language language() const const
auto asKeyValueRange() &
bool empty() const const
QMap::size_type size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString left(qsizetype n) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QString toLower() const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
QStringView mid(qsizetype start, qsizetype length) const const
float toFloat(bool *ok) const const
CaseInsensitive
LeftToRight
SkipEmptyParts
QString host(QUrl::ComponentFormattingOptions options) const const
QString path(QUrl::ComponentFormattingOptions options) const const
QLocale toLocale() const const