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