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