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