Cutelyst  3.1.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 #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 GrantleeView::GrantleeView(QObject *parent, const QString &name) : View(new GrantleeViewPrivate, parent, name)
43 {
44  Q_D(GrantleeView);
45 
46  Grantlee::registerMetaType<ParamsMultiMap>();
47 
48  d->loader = QSharedPointer<Grantlee::FileSystemTemplateLoader>(new Grantlee::FileSystemTemplateLoader);
49 
50  d->engine = new Grantlee::Engine(this);
51  d->engine->addTemplateLoader(d->loader);
52 
53  // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of grantlee engine
54  const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv("CUTELYST_PLUGINS_DIR").split(';');
55  for (const QByteArray &dir : dirs) {
56  d->engine->addPluginPath(QString::fromLocal8Bit(dir));
57  }
58 
59  d->engine->addDefaultLibrary(QStringLiteral("grantlee_cutelyst"));
60 
61  auto app = qobject_cast<Application *>(parent);
62  if (app) {
63  // make sure templates can be found on the current directory
64  setIncludePaths({ app->config(QStringLiteral("root")).toString() });
65 
66  // If CUTELYST_VAR is set the template might have become
67  // {{ Cutelyst.req.base }} instead of {{ c.req.base }}
68  d->cutelystVar = app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
69 
70  app->loadTranslations(QStringLiteral("plugin_view_grantlee"));
71  } else {
72  // make sure templates can be found on the current directory
74  }
75 }
76 
77 QStringList GrantleeView::includePaths() const
78 {
79  Q_D(const GrantleeView);
80  return d->includePaths;
81 }
82 
84 {
85  Q_D(GrantleeView);
86  d->loader->setTemplateDirs(paths);
87  d->includePaths = paths;
88  Q_EMIT changed();
89 }
90 
91 QString GrantleeView::templateExtension() const
92 {
93  Q_D(const GrantleeView);
94  return d->extension;
95 }
96 
98 {
99  Q_D(GrantleeView);
100  d->extension = extension;
101  Q_EMIT changed();
102 }
103 
104 QString GrantleeView::wrapper() const
105 {
106  Q_D(const GrantleeView);
107  return d->wrapper;
108 }
109 
111 {
112  Q_D(GrantleeView);
113  d->wrapper = name;
114  Q_EMIT changed();
115 }
116 
117 void GrantleeView::setCache(bool enable)
118 {
119  Q_D(GrantleeView);
120 
121  if (enable != d->cache.isNull()) {
122  return; // already enabled
123  }
124 
125  delete d->engine;
126  d->engine = new Grantlee::Engine(this);
127 
128  if (enable) {
129  d->cache = QSharedPointer<Grantlee::CachingLoaderDecorator>(new Grantlee::CachingLoaderDecorator(d->loader));
130  d->engine->addTemplateLoader(d->cache);
131  } else {
132  d->cache.clear();
133  d->engine->addTemplateLoader(d->loader);
134  }
135  Q_EMIT changed();
136 }
137 
138 Grantlee::Engine *GrantleeView::engine() const
139 {
140  Q_D(const GrantleeView);
141  return d->engine;
142 }
143 
145 {
146  Q_D(GrantleeView);
147 
148  if (!isCaching()) {
149  setCache(true);
150  }
151 
152  const auto includePaths = d->includePaths;
153  for (const QString &includePath : includePaths) {
154  QDirIterator it(includePath, {
155  QLatin1Char('*') + d->extension
156  },
159  while (it.hasNext()) {
160  QString path = it.next();
161  path.remove(includePath);
162  if (path.startsWith(QLatin1Char('/'))) {
163  path.remove(0, 1);
164  }
165 
166  if (d->cache->canLoadTemplate(path)) {
167  d->cache->loadByName(path, d->engine);
168  }
169  }
170  }
171 }
172 
174 {
175  Q_D(const GrantleeView);
176  return !d->cache.isNull();
177 }
178 
180 {
181  Q_D(const GrantleeView);
182 
183  QByteArray ret;
184  c->setStash(d->cutelystVar, QVariant::fromValue(c));
185  const QVariantHash stash = c->stash();
186  auto it = stash.constFind(QStringLiteral("template"));
187  QString templateFile;
188  if (it != stash.constEnd()) {
189  templateFile = it.value().toString();
190  } else {
191  if (c->action() && !c->action()->reverse().isEmpty()) {
192  templateFile = c->action()->reverse() + d->extension;
193  if (templateFile.startsWith(QLatin1Char('/'))) {
194  templateFile.remove(0, 1);
195  }
196  }
197 
198  if (templateFile.isEmpty()) {
199  c->error(QStringLiteral("Cannot render template, template name or template stash key not defined"));
200  return ret;
201  }
202  }
203 
204  qCDebug(CUTELYST_GRANTLEE) << "Rendering template" << templateFile;
205 
206  Grantlee::Context gc(stash);
207 
209 
210  auto transIt = d->translators.constFind(c->locale());
211  if (transIt != d->translators.constEnd()) {
212  localizer.data()->installTranslator(transIt.value(), transIt.key().name());
213  }
214 
215  auto catalogIt = d->translationCatalogs.constBegin();
216  while (catalogIt != d->translationCatalogs.constEnd()) {
217  localizer.data()->loadCatalog(catalogIt.value(), catalogIt.key());
218  ++it;
219  }
220 
221  gc.setLocalizer(localizer);
222 
223  Grantlee::Template tmpl = d->engine->loadByName(templateFile);
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  QString content = tmpl->render(&gc);
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  if (!d->wrapper.isEmpty()) {
238  Grantlee::Template wrapper = d->engine->loadByName(d->wrapper);
239  if (tmpl->error() != Grantlee::NoError) {
240  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
241  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
242  return ret;
243  }
244 
245  Grantlee::SafeString safeContent(content, true);
246  gc.insert(QStringLiteral("content"), safeContent);
247  content = wrapper->render(&gc);
248 
249  if (wrapper->error() != Grantlee::NoError) {
250  c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
251  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
252  return ret;
253  }
254  }
255 
256  ret = content.toUtf8();
257  return ret;
258 }
259 
260 void GrantleeView::addTranslator(const QLocale &locale, QTranslator *translator)
261 {
262  Q_D(GrantleeView);
263  Q_ASSERT_X(translator, "add translator to GrantleeView", "invalid QTranslator object");
264  d->translators.insert(locale, translator);
265 }
266 
267 void GrantleeView::addTranslator(const QString &locale, QTranslator *translator)
268 {
269  addTranslator(QLocale(locale), translator);
270 }
271 
272 void GrantleeView::addTranslationCatalog(const QString &path, const QString &catalog)
273 {
274  Q_D(GrantleeView);
275  Q_ASSERT_X(!path.isEmpty(), "add translation catalog to GrantleeView", "empty path");
276  Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to GrantleeView", "empty catalog name");
277  d->translationCatalogs.insert(catalog, path);
278 }
279 
281 {
282  Q_D(GrantleeView);
283  Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
284  d->translationCatalogs.unite(catalogs);
285 }
286 
287 QVector<QLocale> GrantleeView::loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix, const QString &suffix)
288 {
289  QVector<QLocale> locales;
290 
291  if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
292  const QDir i18nDir(directory);
293  if (Q_LIKELY(i18nDir.exists())) {
294  const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
295  const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
296  const QStringList namesFilter = 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 = fn.mid(prefIdx + _prefix.length(), fn.length() - prefIdx - _suffix.length() - _prefix.length());
304  QLocale loc(locString);
305  if (Q_LIKELY(loc.language() != QLocale::C)) {
306  auto trans = new QTranslator(this);
307  if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
308  addTranslator(loc, trans);
309  locales.append(loc);
310  qCDebug(CUTELYST_GRANTLEE) << "Loaded translations for locale" << loc << "from" << ts.absoluteFilePath();
311  } else {
312  delete trans;
313  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations for locale" << loc;
314  }
315  } else {
316  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations for invalid locale string" << locString;
317  }
318  }
319  locales.squeeze();
320  } else {
321  qCWarning(CUTELYST_GRANTLEE) << "Can not find translation files for" << filename << "in directory" << directory;
322  }
323  } else {
324  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations from not existing directory:" << directory;
325  }
326  } else {
327  qCWarning(CUTELYST_GRANTLEE) << "Can not load translations for empty file name or empty path.";
328  }
329 
330  return locales;
331 }
332 
333 #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
Response * res() const
Definition: context.cpp:116
void stash(const QVariantHash &unite)
Definition: context.cpp:549
bool error() const
Returns true if an error was set.
Definition: context.cpp:63
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:480
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:225
QLocale locale() const
Definition: context.cpp:456
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()