Cutelyst  3.4.0
grantleeview.cpp
1 /*
2  * Copyright (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 #include "grantleeview_p.h"
19 
20 #include "application.h"
21 #include "context.h"
22 #include "action.h"
23 #include "response.h"
24 #include "config.h"
25 
26 #include <grantlee/qtlocalizer.h>
27 #include <grantlee/metatype.h>
28 
29 #include <QString>
30 #include <QDirIterator>
31 #include <QtCore/QLoggingCategory>
32 #include <QTranslator>
33 
34 Q_LOGGING_CATEGORY(CUTELYST_GRANTLEE, "cutelyst.grantlee", QtWarningMsg)
35 
36 using namespace Cutelyst;
37 
38 GRANTLEE_BEGIN_LOOKUP(ParamsMultiMap)
39 return object.value(property);
40 GRANTLEE_END_LOOKUP
41 
42 GRANTLEE_BEGIN_LOOKUP_PTR(Cutelyst::Request)
43 return object->property(property.toLatin1().constData());
44 GRANTLEE_END_LOOKUP
45 
46 GrantleeView::GrantleeView(QObject *parent, const QString &name) : View(new GrantleeViewPrivate, parent, name)
47 {
48  Q_D(GrantleeView);
49 
50  Grantlee::registerMetaType<ParamsMultiMap>();
51  Grantlee::registerMetaType<Cutelyst::Request*>(); // To be able to access it's properties
52 
53  d->loader = QSharedPointer<Grantlee::FileSystemTemplateLoader>(new Grantlee::FileSystemTemplateLoader);
54 
55  d->engine = new Grantlee::Engine(this);
56  d->engine->addTemplateLoader(d->loader);
57 
58  // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of grantlee engine
59  const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv("CUTELYST_PLUGINS_DIR").split(';');
60  for (const QByteArray &dir : dirs) {
61  d->engine->addPluginPath(QString::fromLocal8Bit(dir));
62  }
63 
64  d->engine->addDefaultLibrary(QStringLiteral("grantlee_cutelyst"));
65 
66  auto app = qobject_cast<Application *>(parent);
67  if (app) {
68  // make sure templates can be found on the current directory
69  setIncludePaths({ app->config(QStringLiteral("root")).toString() });
70 
71  // If CUTELYST_VAR is set the template might have become
72  // {{ Cutelyst.req.base }} instead of {{ c.req.base }}
73  d->cutelystVar = app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
74 
75  app->loadTranslations(QStringLiteral("plugin_view_grantlee"));
76  } else {
77  // make sure templates can be found on the current directory
79  }
80 }
81 
82 QStringList GrantleeView::includePaths() const
83 {
84  Q_D(const GrantleeView);
85  return d->includePaths;
86 }
87 
89 {
90  Q_D(GrantleeView);
91  d->loader->setTemplateDirs(paths);
92  d->includePaths = paths;
93  Q_EMIT changed();
94 }
95 
96 QString GrantleeView::templateExtension() const
97 {
98  Q_D(const GrantleeView);
99  return d->extension;
100 }
101 
103 {
104  Q_D(GrantleeView);
105  d->extension = extension;
106  Q_EMIT changed();
107 }
108 
109 QString GrantleeView::wrapper() const
110 {
111  Q_D(const GrantleeView);
112  return d->wrapper;
113 }
114 
116 {
117  Q_D(GrantleeView);
118  d->wrapper = name;
119  Q_EMIT changed();
120 }
121 
122 void GrantleeView::setCache(bool enable)
123 {
124  Q_D(GrantleeView);
125 
126  if (enable != d->cache.isNull()) {
127  return; // already enabled
128  }
129 
130  delete d->engine;
131  d->engine = new Grantlee::Engine(this);
132 
133  if (enable) {
134  d->cache = QSharedPointer<Grantlee::CachingLoaderDecorator>(new Grantlee::CachingLoaderDecorator(d->loader));
135  d->engine->addTemplateLoader(d->cache);
136  } else {
137  d->cache.clear();
138  d->engine->addTemplateLoader(d->loader);
139  }
140  Q_EMIT changed();
141 }
142 
143 Grantlee::Engine *GrantleeView::engine() const
144 {
145  Q_D(const GrantleeView);
146  return d->engine;
147 }
148 
150 {
151  Q_D(GrantleeView);
152 
153  if (!isCaching()) {
154  setCache(true);
155  }
156 
157  const auto includePaths = d->includePaths;
158  for (const QString &includePath : includePaths) {
159  QDirIterator it(includePath, {
160  QLatin1Char('*') + d->extension
161  },
164  while (it.hasNext()) {
165  QString path = it.next();
166  path.remove(includePath);
167  if (path.startsWith(QLatin1Char('/'))) {
168  path.remove(0, 1);
169  }
170 
171  if (d->cache->canLoadTemplate(path)) {
172  d->cache->loadByName(path, d->engine);
173  }
174  }
175  }
176 }
177 
179 {
180  Q_D(const GrantleeView);
181  return !d->cache.isNull();
182 }
183 
185 {
186  Q_D(const GrantleeView);
187 
188  QByteArray ret;
189  c->setStash(d->cutelystVar, QVariant::fromValue(c));
190  const QVariantHash stash = c->stash();
191  auto it = stash.constFind(QStringLiteral("template"));
192  QString templateFile;
193  if (it != stash.constEnd()) {
194  templateFile = it.value().toString();
195  } else {
196  if (c->action() && !c->action()->reverse().isEmpty()) {
197  templateFile = c->action()->reverse() + d->extension;
198  if (templateFile.startsWith(QLatin1Char('/'))) {
199  templateFile.remove(0, 1);
200  }
201  }
202 
203  if (templateFile.isEmpty()) {
204  c->error(QStringLiteral("Cannot render template, template name or template stash key not defined"));
205  return ret;
206  }
207  }
208 
209  qCDebug(CUTELYST_GRANTLEE) << "Rendering template" << templateFile;
210 
211  Grantlee::Context gc(stash);
212 
214 
215  auto transIt = d->translators.constFind(c->locale());
216  if (transIt != d->translators.constEnd()) {
217  localizer.data()->installTranslator(transIt.value(), transIt.key().name());
218  }
219 
220  auto catalogIt = d->translationCatalogs.constBegin();
221  while (catalogIt != d->translationCatalogs.constEnd()) {
222  localizer.data()->loadCatalog(catalogIt.value(), catalogIt.key());
223  ++it;
224  }
225 
226  gc.setLocalizer(localizer);
227 
228  Grantlee::Template tmpl = d->engine->loadByName(templateFile);
229  if (tmpl->error() != Grantlee::NoError) {
230  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
231  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
232  return ret;
233  }
234 
235  QString content = tmpl->render(&gc);
236  if (tmpl->error() != Grantlee::NoError) {
237  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
238  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
239  return ret;
240  }
241 
242  if (!d->wrapper.isEmpty()) {
243  Grantlee::Template wrapper = d->engine->loadByName(d->wrapper);
244  if (tmpl->error() != Grantlee::NoError) {
245  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
246  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
247  return ret;
248  }
249 
250  Grantlee::SafeString safeContent(content, true);
251  gc.insert(QStringLiteral("content"), safeContent);
252  content = wrapper->render(&gc);
253 
254  if (wrapper->error() != Grantlee::NoError) {
255  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
256  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
257  return ret;
258  }
259  }
260 
261  ret = content.toUtf8();
262  return ret;
263 }
264 
265 void GrantleeView::addTranslator(const QLocale &locale, QTranslator *translator)
266 {
267  Q_D(GrantleeView);
268  Q_ASSERT_X(translator, "add translator to GrantleeView", "invalid QTranslator object");
269  d->translators.insert(locale, translator);
270 }
271 
272 void GrantleeView::addTranslator(const QString &locale, QTranslator *translator)
273 {
274  addTranslator(QLocale(locale), translator);
275 }
276 
277 void GrantleeView::addTranslationCatalog(const QString &path, const QString &catalog)
278 {
279  Q_D(GrantleeView);
280  Q_ASSERT_X(!path.isEmpty(), "add translation catalog to GrantleeView", "empty path");
281  Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to GrantleeView", "empty catalog name");
282  d->translationCatalogs.insert(catalog, path);
283 }
284 
286 {
287  Q_D(GrantleeView);
288  Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
289  d->translationCatalogs.unite(catalogs);
290 }
291 
292 QVector<QLocale> GrantleeView::loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix, const QString &suffix)
293 {
294  QVector<QLocale> locales;
295 
296  if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
297  const QDir i18nDir(directory);
298  if (Q_LIKELY(i18nDir.exists())) {
299  const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
300  const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
301  const QStringList namesFilter = QStringList({filename + _prefix + QLatin1Char('*') + _suffix});
302  const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
303  if (Q_LIKELY(!tsFiles.empty())) {
304  locales.reserve(tsFiles.size());
305  for (const QFileInfo &ts : tsFiles) {
306  const QString fn = ts.fileName();
307  const int prefIdx = fn.indexOf(_prefix);
308  const QString locString = fn.mid(prefIdx + _prefix.length(), fn.length() - prefIdx - _suffix.length() - _prefix.length());
309  QLocale loc(locString);
310  if (Q_LIKELY(loc.language() != QLocale::C)) {
311  auto trans = new QTranslator(this);
312  if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
313  addTranslator(loc, trans);
314  locales.append(loc);
315  qCDebug(CUTELYST_GRANTLEE) << "Loaded translations for locale" << loc << "from" << ts.absoluteFilePath();
316  } else {
317  delete trans;
318  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations for locale" << loc;
319  }
320  } else {
321  qCWarning(CUTELYST_GRANTLEE) << "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 << "in directory" << directory;
327  }
328  } else {
329  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations from not existing directory:" << directory;
330  }
331  } else {
332  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations for empty file name or empty path.";
333  }
334 
335  return locales;
336 }
337 
338 #include "moc_grantleeview.cpp"
QString name() const
Definition: component.cpp:44
QString reverse() const
Definition: component.cpp:56
The Cutelyst Context.
Definition: context.h:52
void stash(const QVariantHash &unite)
Definition: context.cpp:553
QLocale locale() const noexcept
Definition: context.cpp:460
Response * res() const noexcept
Definition: context.cpp:116
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:225
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:63
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:114
Cutelyst View abstract view component
Definition: view.h:35
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()