Cutelyst  3.5.0
grantleeview.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "grantleeview_p.h"
6 
7 #include "application.h"
8 #include "context.h"
9 #include "action.h"
10 #include "response.h"
11 #include "config.h"
12 
13 #include <grantlee/qtlocalizer.h>
14 #include <grantlee/metatype.h>
15 
16 #include <QString>
17 #include <QDirIterator>
18 #include <QtCore/QLoggingCategory>
19 #include <QTranslator>
20 
21 Q_LOGGING_CATEGORY(CUTELYST_GRANTLEE, "cutelyst.grantlee", QtWarningMsg)
22 
23 using namespace Cutelyst;
24 
25 GRANTLEE_BEGIN_LOOKUP(ParamsMultiMap)
26 return object.value(property);
27 GRANTLEE_END_LOOKUP
28 
29 GRANTLEE_BEGIN_LOOKUP_PTR(Cutelyst::Request)
30 return object->property(property.toLatin1().constData());
31 GRANTLEE_END_LOOKUP
32 
33 GrantleeView::GrantleeView(QObject *parent, const QString &name) : View(new GrantleeViewPrivate, parent, name)
34 {
35  Q_D(GrantleeView);
36 
37  Grantlee::registerMetaType<ParamsMultiMap>();
38  Grantlee::registerMetaType<Cutelyst::Request*>(); // To be able to access it's properties
39 
40  d->loader = QSharedPointer<Grantlee::FileSystemTemplateLoader>(new Grantlee::FileSystemTemplateLoader);
41 
42  d->engine = new Grantlee::Engine(this);
43  d->engine->addTemplateLoader(d->loader);
44 
45  // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of grantlee engine
46  const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv("CUTELYST_PLUGINS_DIR").split(';');
47  for (const QByteArray &dir : dirs) {
48  d->engine->addPluginPath(QString::fromLocal8Bit(dir));
49  }
50 
51  d->engine->addDefaultLibrary(QStringLiteral("grantlee_cutelyst"));
52 
53  auto app = qobject_cast<Application *>(parent);
54  if (app) {
55  // make sure templates can be found on the current directory
56  setIncludePaths({ app->config(QStringLiteral("root")).toString() });
57 
58  // If CUTELYST_VAR is set the template might have become
59  // {{ Cutelyst.req.base }} instead of {{ c.req.base }}
60  d->cutelystVar = app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
61 
62  app->loadTranslations(QStringLiteral("plugin_view_grantlee"));
63  } else {
64  // make sure templates can be found on the current directory
66  }
67 }
68 
70 {
71  Q_D(const GrantleeView);
72  return d->includePaths;
73 }
74 
76 {
77  Q_D(GrantleeView);
78  d->loader->setTemplateDirs(paths);
79  d->includePaths = paths;
80  Q_EMIT changed();
81 }
82 
84 {
85  Q_D(const GrantleeView);
86  return d->extension;
87 }
88 
90 {
91  Q_D(GrantleeView);
92  d->extension = extension;
93  Q_EMIT changed();
94 }
95 
97 {
98  Q_D(const GrantleeView);
99  return d->wrapper;
100 }
101 
103 {
104  Q_D(GrantleeView);
105  d->wrapper = name;
106  Q_EMIT changed();
107 }
108 
109 void GrantleeView::setCache(bool enable)
110 {
111  Q_D(GrantleeView);
112 
113  if (enable != d->cache.isNull()) {
114  return; // already enabled
115  }
116 
117  delete d->engine;
118  d->engine = new Grantlee::Engine(this);
119 
120  if (enable) {
121  d->cache = QSharedPointer<Grantlee::CachingLoaderDecorator>(new Grantlee::CachingLoaderDecorator(d->loader));
122  d->engine->addTemplateLoader(d->cache);
123  } else {
124  d->cache.clear();
125  d->engine->addTemplateLoader(d->loader);
126  }
127  Q_EMIT changed();
128 }
129 
130 Grantlee::Engine *GrantleeView::engine() const
131 {
132  Q_D(const GrantleeView);
133  return d->engine;
134 }
135 
137 {
138  Q_D(GrantleeView);
139 
140  if (!isCaching()) {
141  setCache(true);
142  }
143 
144  const auto includePaths = d->includePaths;
145  for (const QString &includePath : includePaths) {
146  QDirIterator it(includePath, {
147  QLatin1Char('*') + d->extension
148  },
149  QDir::Files | QDir::NoDotAndDotDot,
150  QDirIterator::Subdirectories);
151  while (it.hasNext()) {
152  QString path = it.next();
153  path.remove(includePath);
154  if (path.startsWith(QLatin1Char('/'))) {
155  path.remove(0, 1);
156  }
157 
158  if (d->cache->canLoadTemplate(path)) {
159  d->cache->loadByName(path, d->engine);
160  }
161  }
162  }
163 }
164 
166 {
167  Q_D(const GrantleeView);
168  return !d->cache.isNull();
169 }
170 
172 {
173  Q_D(const GrantleeView);
174 
175  QByteArray ret;
176  c->setStash(d->cutelystVar, QVariant::fromValue(c));
177  const QVariantHash stash = c->stash();
178  auto it = stash.constFind(QStringLiteral("template"));
179  QString templateFile;
180  if (it != stash.constEnd()) {
181  templateFile = it.value().toString();
182  } else {
183  if (c->action() && !c->action()->reverse().isEmpty()) {
184  templateFile = c->action()->reverse() + d->extension;
185  if (templateFile.startsWith(QLatin1Char('/'))) {
186  templateFile.remove(0, 1);
187  }
188  }
189 
190  if (templateFile.isEmpty()) {
191  c->error(QStringLiteral("Cannot render template, template name or template stash key not defined"));
192  return ret;
193  }
194  }
195 
196  qCDebug(CUTELYST_GRANTLEE) << "Rendering template" << templateFile;
197 
198  Grantlee::Context gc(stash);
199 
201 
202  auto transIt = d->translators.constFind(c->locale());
203  if (transIt != d->translators.constEnd()) {
204  localizer.data()->installTranslator(transIt.value(), transIt.key().name());
205  }
206 
207  auto catalogIt = d->translationCatalogs.constBegin();
208  while (catalogIt != d->translationCatalogs.constEnd()) {
209  localizer.data()->loadCatalog(catalogIt.value(), catalogIt.key());
210  ++it;
211  }
212 
213  gc.setLocalizer(localizer);
214 
215  Grantlee::Template tmpl = d->engine->loadByName(templateFile);
216  if (tmpl->error() != Grantlee::NoError) {
217  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
218  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
219  return ret;
220  }
221 
222  QString content = tmpl->render(&gc);
223  if (tmpl->error() != Grantlee::NoError) {
224  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
225  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
226  return ret;
227  }
228 
229  if (!d->wrapper.isEmpty()) {
230  Grantlee::Template wrapper = d->engine->loadByName(d->wrapper);
231  if (tmpl->error() != Grantlee::NoError) {
232  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
233  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
234  return ret;
235  }
236 
237  Grantlee::SafeString safeContent(content, true);
238  gc.insert(QStringLiteral("content"), safeContent);
239  content = wrapper->render(&gc);
240 
241  if (wrapper->error() != Grantlee::NoError) {
242  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
243  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
244  return ret;
245  }
246  }
247 
248  ret = content.toUtf8();
249  return ret;
250 }
251 
252 void GrantleeView::addTranslator(const QLocale &locale, QTranslator *translator)
253 {
254  Q_D(GrantleeView);
255  Q_ASSERT_X(translator, "add translator to GrantleeView", "invalid QTranslator object");
256  d->translators.insert(locale, translator);
257 }
258 
259 void GrantleeView::addTranslator(const QString &locale, QTranslator *translator)
260 {
261  addTranslator(QLocale(locale), translator);
262 }
263 
264 void GrantleeView::addTranslationCatalog(const QString &path, const QString &catalog)
265 {
266  Q_D(GrantleeView);
267  Q_ASSERT_X(!path.isEmpty(), "add translation catalog to GrantleeView", "empty path");
268  Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to GrantleeView", "empty catalog name");
269  d->translationCatalogs.insert(catalog, path);
270 }
271 
273 {
274  Q_D(GrantleeView);
275  Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
276  d->translationCatalogs.unite(catalogs);
277 }
278 
279 QVector<QLocale> GrantleeView::loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix, const QString &suffix)
280 {
281  QVector<QLocale> locales;
282 
283  if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
284  const QDir i18nDir(directory);
285  if (Q_LIKELY(i18nDir.exists())) {
286  const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
287  const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
288  const QStringList namesFilter = QStringList({filename + _prefix + QLatin1Char('*') + _suffix});
289  const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
290  if (Q_LIKELY(!tsFiles.empty())) {
291  locales.reserve(tsFiles.size());
292  for (const QFileInfo &ts : tsFiles) {
293  const QString fn = ts.fileName();
294  const int prefIdx = fn.indexOf(_prefix);
295  const QString locString = fn.mid(prefIdx + _prefix.length(), fn.length() - prefIdx - _suffix.length() - _prefix.length());
296  QLocale loc(locString);
297  if (Q_LIKELY(loc.language() != QLocale::C)) {
298  auto trans = new QTranslator(this);
299  if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
300  addTranslator(loc, trans);
301  locales.append(loc);
302  qCDebug(CUTELYST_GRANTLEE) << "Loaded translations for locale" << loc << "from" << ts.absoluteFilePath();
303  } else {
304  delete trans;
305  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations for locale" << loc;
306  }
307  } else {
308  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations for invalid locale string" << locString;
309  }
310  }
311  locales.squeeze();
312  } else {
313  qCWarning(CUTELYST_GRANTLEE) << "Can not find translation files for" << filename << "in directory" << directory;
314  }
315  } else {
316  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations from not existing directory:" << directory;
317  }
318  } else {
319  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations for empty file name or empty path.";
320  }
321 
322  return locales;
323 }
324 
325 #include "moc_grantleeview.cpp"
bool empty() const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QStringList includePaths() const
Returns the list of include paths.
QSharedPointer< T > create(Args &&... args)
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:50
void append(const T &value)
bool isCaching() const
Returns true if caching is enabled.
Response * res() const noexcept
Definition: context.cpp:103
void setIncludePaths(const QStringList &paths)
Sets the list of include paths which will be looked for when resolving templates files.
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:212
QString & remove(int position, int n)
QString currentPath()
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
QByteArray render(Context *c) const final
The Cutelyst Context.
Definition: context.h:38
QString fromLocal8Bit(const char *str, int size)
bool exists() const const
void stash(const QVariantHash &unite)
Definition: context.cpp:540
QString name() const
Definition: component.cpp:31
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
GrantleeView(QObject *parent=nullptr, const QString &name=QString())
Constructs a GrantleeView object with the given parent and name.
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:471
QLocale::Language language() const const
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void setWrapper(const QString &name)
Sets the template wrapper name, the template will be rendered into content variable in which the wrap...
QString wrapper() const
Returns the template wrapper.
QString reverse() const
Definition: component.cpp:43
QLocale locale() const noexcept
Definition: context.cpp:447
void squeeze()
void reserve(int size)
void setTemplateExtension(const QString &extension)
Sets the template extension, defaults to ".html".
void addTranslationCatalogs(const QHash< QString, QString > &catalogs)
QVariant fromValue(const T &value)
QString mid(int position, int n) const const
QString templateExtension() const
Returns the template extension.
Cutelyst View abstract view component
Definition: view.h:21
int length() const const
The Cutelyst Application.
Definition: application.h:42
void addTranslator(const QLocale &locale, QTranslator *translator)
void setBody(QIODevice *body)
Definition: response.cpp:101
QObject * parent() const const
Grantlee::Engine * engine() const
void addTranslationCatalog(const QString &path, const QString &catalog)
void setCache(bool enable)
Sets if template caching should be done, this increases performance at the cost of higher memory usag...
QByteArray toUtf8() const const