cutelyst  3.7.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 "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
65  setIncludePaths({ QDir::currentPath() });
66  }
67 }
68 
69 QStringList GrantleeView::includePaths() const
70 {
71  Q_D(const GrantleeView);
72  return d->includePaths;
73 }
74 
75 void GrantleeView::setIncludePaths(const QStringList &paths)
76 {
77  Q_D(GrantleeView);
78  d->loader->setTemplateDirs(paths);
79  d->includePaths = paths;
80  Q_EMIT changed();
81 }
82 
83 QString GrantleeView::templateExtension() const
84 {
85  Q_D(const GrantleeView);
86  return d->extension;
87 }
88 
89 void GrantleeView::setTemplateExtension(const QString &extension)
90 {
91  Q_D(GrantleeView);
92  d->extension = extension;
93  Q_EMIT changed();
94 }
95 
96 QString GrantleeView::wrapper() const
97 {
98  Q_D(const GrantleeView);
99  return d->wrapper;
100 }
101 
102 void GrantleeView::setWrapper(const QString &name)
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 
171 QByteArray GrantleeView::render(Context *c) const
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 
200  auto localizer = QSharedPointer<Grantlee::QtLocalizer>::create(c->locale());
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 
272 void GrantleeView::addTranslationCatalogs(const QHash<QString, QString> &catalogs)
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"
QString name() const
Definition: component.cpp:31
QString reverse() const
Definition: component.cpp:43
The Cutelyst Context.
Definition: context.h:39
void stash(const QVariantHash &unite)
Definition: context.cpp:546
QLocale locale() const noexcept
Definition: context.cpp:453
Response * res() const noexcept
Definition: context.cpp:103
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:477
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:218
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:50
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:101
Cutelyst View abstract view component
Definition: view.h:22
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
QMultiMap< QString, QString > ParamsMultiMap