cutelyst  3.9.1
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 
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 
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 
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"
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:49
void append(const T &value)
bool isCaching() const
Returns true if caching is enabled.
Response * res() const noexcept
Definition: context.cpp:102
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:217
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:566
QString name() const
Definition: component.cpp:33
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:490
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:45
QLocale locale() const noexcept
Definition: context.cpp:466
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:100
T qobject_cast(QObject *object)
QObject * parent() const const
Grantlee::Engine * engine() const
void addTranslationCatalog(const QString &path, const QString &catalog)
Q_EMITQ_EMIT
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