1 /*
2   This file is part of KAddressBook.
3 
4   SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
5 
6   SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "grantleecontactformatter.h"
10 #include "contactgrantleewrapper.h"
11 
12 #include <KContacts/Addressee>
13 
14 #include <GrantleeTheme/GrantleeKi18nLocalizer>
15 #include <GrantleeTheme/GrantleeTheme>
16 #include <GrantleeTheme/GrantleeThemeEngine>
17 
18 #include <grantlee/context.h>
19 #include <grantlee/metatype.h>
20 #include <grantlee/templateloader.h>
21 
22 #include <Akonadi/Item>
23 
24 #include <KColorScheme>
25 
26 #include <KConfigGroup>
27 #include <KLocalizedString>
28 #include <KStringHandler>
29 
30 #include <QLocale>
31 #include <QMetaProperty>
32 #include <QSet>
33 
34 using namespace KAddressBookGrantlee;
35 
36 GRANTLEE_BEGIN_LOOKUP(QUrl)
37 if (property == QLatin1String("scheme")) {
38     return object.scheme();
39 } else if (property == QLatin1String("path")) {
40     return object.path();
41 }
42 GRANTLEE_END_LOOKUP
43 
44 class KAddressBookGrantlee::GrantleeContactFormatterPrivate
45 {
46 public:
GrantleeContactFormatterPrivate()47     GrantleeContactFormatterPrivate()
48     {
49         KConfig config(QStringLiteral("akonadi_contactrc"));
50         KConfigGroup group(&config, QStringLiteral("View"));
51         showQRCode = group.readEntry("QRCodes", true);
52 
53         mEngine = std::make_unique<GrantleeTheme::Engine>();
54 
55         mTemplateLoader = QSharedPointer<Grantlee::FileSystemTemplateLoader>(new Grantlee::FileSystemTemplateLoader());
56     }
57 
~GrantleeContactFormatterPrivate()58     ~GrantleeContactFormatterPrivate()
59     {
60         mTemplateLoader.clear();
61     }
62 
changeGrantleePath(const QString & path)63     void changeGrantleePath(const QString &path)
64     {
65         mTemplateLoader->setTemplateDirs(QStringList() << path);
66         mEngine->addTemplateLoader(mTemplateLoader);
67 
68         mSelfcontainedTemplate = mEngine->loadByName(QStringLiteral("contact.html"));
69         if (mSelfcontainedTemplate->error()) {
70             mErrorMessage += mSelfcontainedTemplate->errorString() + QStringLiteral("<br>");
71         }
72 
73         mEmbeddableTemplate = mEngine->loadByName(QStringLiteral("contact_embedded.html"));
74         if (mEmbeddableTemplate->error()) {
75             mErrorMessage += mEmbeddableTemplate->errorString() + QStringLiteral("<br>");
76         }
77     }
78 
79     QVector<QObject *> mObjects;
80     std::unique_ptr<GrantleeTheme::Engine> mEngine;
81     QSharedPointer<Grantlee::FileSystemTemplateLoader> mTemplateLoader;
82     Grantlee::Template mSelfcontainedTemplate;
83     Grantlee::Template mEmbeddableTemplate;
84     QString mErrorMessage;
85     bool forceDisableQRCode = false;
86     bool showQRCode = true;
87 };
88 
GrantleeContactFormatter()89 GrantleeContactFormatter::GrantleeContactFormatter()
90     : d(new GrantleeContactFormatterPrivate)
91 {
92     Grantlee::registerMetaType<QUrl>();
93 }
94 
95 GrantleeContactFormatter::~GrantleeContactFormatter() = default;
96 
setAbsoluteThemePath(const QString & path)97 void GrantleeContactFormatter::setAbsoluteThemePath(const QString &path)
98 {
99     d->changeGrantleePath(path);
100 }
101 
setGrantleeTheme(const GrantleeTheme::Theme & theme)102 void GrantleeContactFormatter::setGrantleeTheme(const GrantleeTheme::Theme &theme)
103 {
104     d->changeGrantleePath(theme.absolutePath());
105 }
106 
setForceDisableQRCode(bool b)107 void GrantleeContactFormatter::setForceDisableQRCode(bool b)
108 {
109     d->forceDisableQRCode = b;
110 }
111 
forceDisableQRCode() const112 bool GrantleeContactFormatter::forceDisableQRCode() const
113 {
114     return d->forceDisableQRCode;
115 }
116 
setShowQRCode(bool b)117 void GrantleeContactFormatter::setShowQRCode(bool b)
118 {
119     d->showQRCode = b;
120 }
121 
toHtml(HtmlForm form) const122 QString GrantleeContactFormatter::toHtml(HtmlForm form) const
123 {
124     if (!d->mErrorMessage.isEmpty()) {
125         return d->mErrorMessage;
126     }
127 
128     KContacts::Addressee rawContact;
129     const Akonadi::Item localItem = item();
130     if (localItem.isValid() && localItem.hasPayload<KContacts::Addressee>()) {
131         rawContact = localItem.payload<KContacts::Addressee>();
132     } else {
133         rawContact = contact();
134     }
135 
136     if (rawContact.isEmpty()) {
137         return QString();
138     }
139 
140     // Custom fields
141     QVariantList customFields;
142     QVariantList customFieldsUrl;
143     static QSet<QString> blacklistedKeys = {QStringLiteral("CRYPTOPROTOPREF"),
144                                             QStringLiteral("OPENPGPFP"),
145                                             QStringLiteral("SMIMEFP"),
146                                             QStringLiteral("CRYPTOSIGNPREF"),
147                                             QStringLiteral("CRYPTOENCRYPTPREF"),
148                                             QStringLiteral("Anniversary"),
149                                             QStringLiteral("BlogFeed"),
150                                             QStringLiteral("Profession"),
151                                             QStringLiteral("Office"),
152                                             QStringLiteral("ManagersName"),
153                                             QStringLiteral("AssistantsName"),
154                                             QStringLiteral("SpousesName"),
155                                             QStringLiteral("IMAddress"),
156                                             QStringLiteral("AddressBook"),
157                                             QStringLiteral("MailPreferedFormatting"),
158                                             QStringLiteral("MailAllowToRemoteContent"),
159                                             QStringLiteral("MAILPREFEREDFORMATTING"),
160                                             QStringLiteral("MAILALLOWTOREMOTECONTENT")};
161 
162     const auto customs = rawContact.customs();
163     for (QString custom : customs) {
164         if (custom.startsWith(QLatin1String("KADDRESSBOOK-"))) {
165             custom.remove(QStringLiteral("KADDRESSBOOK-X-"));
166             custom.remove(QStringLiteral("KADDRESSBOOK-"));
167 
168             int pos = custom.indexOf(QLatin1Char(':'));
169             QString key = custom.left(pos);
170             QString value = custom.mid(pos + 1);
171 
172             if (blacklistedKeys.contains(key)) {
173                 continue;
174             }
175 
176             bool addUrl = false;
177             // check whether it is a custom local field
178             for (int i = 0; i < customFieldDescriptions().size(); ++i) {
179                 const QVariantMap description = customFieldDescriptions().at(i);
180                 if (description.value(QStringLiteral("key")).toString() == key) {
181                     key = description.value(QStringLiteral("title")).toString();
182                     const QString descriptionType = description.value(QStringLiteral("type")).toString();
183                     if (descriptionType == QLatin1String("boolean")) {
184                         if (value == QLatin1String("true")) {
185                             value = i18nc("Boolean value", "yes");
186                         } else {
187                             value = i18nc("Boolean value", "no");
188                         }
189                     } else if (descriptionType == QLatin1String("date")) {
190                         const QDate date = QDate::fromString(value, Qt::ISODate);
191                         value = QLocale().toString(date, QLocale::ShortFormat);
192                     } else if (descriptionType == QLatin1String("time")) {
193                         const QTime time = QTime::fromString(value, Qt::ISODate);
194                         value = QLocale::system().toString(time, QLocale::ShortFormat);
195                     } else if (descriptionType == QLatin1String("datetime")) {
196                         const QDateTime dateTime = QDateTime::fromString(value, Qt::ISODate);
197                         value = QLocale().toString(dateTime, QLocale::ShortFormat);
198                     } else if (descriptionType == QLatin1String("url")) {
199                         value = KStringHandler::tagUrls(value.toHtmlEscaped());
200                         addUrl = true;
201                     }
202                     break;
203                 }
204             }
205             QVariantHash customFieldObject;
206             customFieldObject.insert(QStringLiteral("title"), key);
207             customFieldObject.insert(QStringLiteral("value"), value);
208 
209             if (addUrl) {
210                 customFieldsUrl.append(customFieldObject);
211             } else {
212                 customFields.append(customFieldObject);
213             }
214         }
215     }
216 
217     QVariantHash colorsObject;
218 
219     colorsObject.insert(QStringLiteral("linkColor"), KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name());
220 
221     colorsObject.insert(QStringLiteral("textColor"), KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name());
222 
223     colorsObject.insert(QStringLiteral("backgroundColor"), KColorScheme(QPalette::Active, KColorScheme::View).background().color().name());
224 
225     QVariantHash mapping;
226     mapping.insert(QStringLiteral("contact"), QVariant::fromValue(ContactGrantleeWrapper(rawContact)));
227     mapping.insert(QStringLiteral("colors"), colorsObject);
228     mapping.insert(QStringLiteral("customFields"), customFields);
229     mapping.insert(QStringLiteral("customFieldsUrl"), customFieldsUrl);
230     mapping.insert(QStringLiteral("hasqrcode"), !d->forceDisableQRCode && d->showQRCode);
231 
232     Grantlee::Context context(mapping);
233     context.setLocalizer(d->mEngine->localizer());
234 
235     if (form == SelfcontainedForm) {
236         return d->mSelfcontainedTemplate->render(&context);
237     } else if (form == EmbeddableForm) {
238         return d->mEmbeddableTemplate->render(&context);
239     } else {
240         return QString();
241     }
242 }
243 
setApplicationDomain(const QByteArray & domain)244 void GrantleeContactFormatter::setApplicationDomain(const QByteArray &domain)
245 {
246     d->mEngine->localizer()->setApplicationDomain(domain);
247 }
248