Akonadi Contacts

standardcontactformatter.cpp
1 /*
2  This file is part of Akonadi Contact.
3 
4  SPDX-FileCopyrightText: 2010 Tobias Koenig <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "standardcontactformatter.h"
10 
11 #include <KColorScheme>
12 #include <KConfigGroup>
13 #include <item.h>
14 #include <kcontacts/addressee.h>
15 
16 #include <KLocalizedString>
17 #include <KStringHandler>
18 
19 #include <QLocale>
20 #include <QRegularExpression>
21 #include <QSet>
22 
23 using namespace Akonadi;
24 
25 class Q_DECL_HIDDEN StandardContactFormatter::Private
26 {
27 public:
28  bool displayQRcode = true;
29 };
30 
32  : d(new Private())
33 {
34 }
35 
37 {
38  delete d;
39 }
40 
41 static int contactAge(const QDate &date)
42 {
43  QDate now = QDate::currentDate();
44  int age = now.year() - date.year();
45  if (date > now.addYears(-age)) {
46  age--;
47  }
48  return age;
49 }
50 
52 {
53  KContacts::Addressee rawContact;
54  const Akonadi::Item localItem = item();
55  if (localItem.isValid() && localItem.hasPayload<KContacts::Addressee>()) {
56  rawContact = localItem.payload<KContacts::Addressee>();
57  } else {
58  rawContact = contact();
59  }
60 
61  if (rawContact.isEmpty()) {
62  return QString();
63  }
64 
65  // We'll be building a table to display the vCard in.
66  // Each row of the table will be built using one of these strings for its HTML.
67 
68  // single data item:
69  // %1 is the item name
70  // %2 is the item value
71  QString rowFmtStr1 = QStringLiteral(
72  "<tr valign=\"top\">"
73  "<td align=\"right\" valign=\"top\" width=\"30%\"><b><font color=\"grey\">%1</font></b></td>\n"
74  "<td colspan=\"2\" align=\"left\" valign=\"top\" width=\"70%\"><font>%2</font></td>\n"
75  "</tr>\n");
76 
77  // data item plus additional icon(s):
78  // %1 is the item name
79  // %2 is the item value
80  // %3 is the icon(s), each as a HTML <a><img> tag
81  QString rowFmtStr2 = QStringLiteral(
82  "<tr valign=\"top\">"
83  "<td align=\"right\" valign=\"top\" width=\"30%\"><b><font color=\"grey\">%1</font></b></td>\n"
84  "<td align=\"left\" valign=\"top\"><font>%2</font></td>\n"
85  "<td align=\"left\" valign=\"top\">%3</td>\n"
86  "</tr>\n");
87 
88  // Build the table's rows here
89  QString dynamicPart;
90 
91  // Birthday
92  const QDate date = rawContact.birthday().date();
93  const int years = contactAge(date);
94 
95  if (date.isValid()) {
96  dynamicPart += rowFmtStr1.arg(KContacts::Addressee::birthdayLabel())
97  .arg(QLocale().toString(date) + QLatin1String("&nbsp;&nbsp;") + i18np("(One year old)", "(%1 years old)", years));
98  }
99 
100  // Phone Numbers
101  int counter = 0;
102 
103  const auto phoneNumbers = rawContact.phoneNumbers();
104  for (const KContacts::PhoneNumber &number : phoneNumbers) {
105  QString dispLabel = number.typeLabel().replace(QLatin1Char(' '), QStringLiteral("&nbsp;"));
106  QString dispValue = QStringLiteral("<a href=\"phone:?index=%1\">%2</a>").arg(counter).arg(number.number().toHtmlEscaped());
107  if (number.type() & KContacts::PhoneNumber::Cell) {
108  QString dispIcon = QStringLiteral("<a href=\"sms:?index=%1\" title=\"%2\"><img src=\"sms_icon\" align=\"top\"/>")
109  .arg(counter)
110  .arg(i18nc("@info:tooltip", "Send SMS"));
111  dynamicPart += rowFmtStr2.arg(dispLabel, dispValue, dispIcon);
112  } else {
113  dynamicPart += rowFmtStr1.arg(dispLabel, dispValue);
114  }
115 
116  ++counter;
117  }
118 
119  // EMails
120  for (const QString &email : rawContact.emails()) {
121  const QString type = i18nc("a contact's email address", "Email");
122 
123  const QString fullEmail = QString::fromLatin1(QUrl::toPercentEncoding(rawContact.fullEmail(email)));
124 
125  dynamicPart += rowFmtStr1.arg(type).arg(QStringLiteral("<a href=\"mailto:%1\">%2</a>").arg(fullEmail, email));
126  }
127 
128  // Homepage
129  if (rawContact.url().isValid()) {
130  QString url = rawContact.url().url().url();
131  if (!url.startsWith(QLatin1String("http://")) && !url.startsWith(QLatin1String("https://"))) {
132  url = QLatin1String("http://") + url;
133  }
134 
136  dynamicPart += rowFmtStr1.arg(i18n("Homepage"), url);
137  }
138 
139  // Blog Feed
140  const QString blog = rawContact.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("BlogFeed"));
141  if (!blog.isEmpty()) {
142  dynamicPart += rowFmtStr1.arg(i18n("Blog Feed")).arg(KStringHandler::tagUrls(blog.toHtmlEscaped()));
143  }
144 
145  // Addresses
146  counter = 0;
147  const auto addresses = rawContact.addresses();
148  for (const KContacts::Address &address : addresses) {
149  QString formattedAddress;
150 
151  if (address.label().isEmpty()) {
152  formattedAddress = address.formattedAddress().trimmed().toHtmlEscaped();
153  } else {
154  formattedAddress = address.label().toHtmlEscaped();
155  }
156 
157  formattedAddress.replace(QRegularExpression(QStringLiteral("\n+")), QStringLiteral("<br>"));
158 
159  const QString url = QStringLiteral("<a href=\"address:?index=%1\" title=\"%2\"><img src=\"map_icon\" alt=\"%2\"/></a>")
160  .arg(counter)
161  .arg(i18nc("@info:tooltip", "Show address on map"));
162  counter++;
163 
164  dynamicPart += rowFmtStr2.arg(KContacts::Address::typeLabel(address.type()), formattedAddress, url);
165  }
166 
167  // Note
168  QString notes;
169  if (!rawContact.note().isEmpty()) {
170  notes = rowFmtStr1.arg(i18n("Notes")).arg(rawContact.note().toHtmlEscaped().replace(QLatin1Char('\n'), QLatin1String("<br>")));
171  }
172 
173  // Custom Data
174  QString customData;
175  static QMap<QString, QString> titleMap;
176  if (titleMap.isEmpty()) {
177  titleMap.insert(QStringLiteral("Department"), i18n("Department"));
178  titleMap.insert(QStringLiteral("Profession"), i18n("Profession"));
179  titleMap.insert(QStringLiteral("AssistantsName"), i18n("Assistant's Name"));
180  titleMap.insert(QStringLiteral("ManagersName"), i18n("Manager's Name"));
181  titleMap.insert(QStringLiteral("SpousesName"), i18nc("Wife/Husband/...", "Partner's Name"));
182  titleMap.insert(QStringLiteral("Office"), i18n("Office"));
183  titleMap.insert(QStringLiteral("IMAddress"), i18n("IM Address"));
184  titleMap.insert(QStringLiteral("Anniversary"), i18n("Anniversary"));
185  titleMap.insert(QStringLiteral("AddressBook"), i18n("Address Book"));
186  const QMap<QString, QString> tmpTitleMap = titleMap;
187 
188  QMap<QString, QString>::const_iterator iterator = tmpTitleMap.constBegin();
189  while (iterator != tmpTitleMap.constEnd()) {
190  titleMap.insert(iterator.key().toUpper(), iterator.value());
191  ++iterator;
192  }
193  }
194 
195  static QSet<QString> blacklistedKeys;
196  if (blacklistedKeys.isEmpty()) {
197  blacklistedKeys.insert(QStringLiteral("CRYPTOPROTOPREF"));
198  blacklistedKeys.insert(QStringLiteral("OPENPGPFP"));
199  blacklistedKeys.insert(QStringLiteral("SMIMEFP"));
200  blacklistedKeys.insert(QStringLiteral("CRYPTOSIGNPREF"));
201  blacklistedKeys.insert(QStringLiteral("CRYPTOENCRYPTPREF"));
202  blacklistedKeys.insert(QStringLiteral("MailPreferedFormatting"));
203  blacklistedKeys.insert(QStringLiteral("MailAllowToRemoteContent"));
204  blacklistedKeys.insert(QStringLiteral("MAILPREFEREDFORMATTING"));
205  blacklistedKeys.insert(QStringLiteral("MAILALLOWTOREMOTECONTENT"));
206  }
207 
208  if (!rawContact.customs().empty()) {
209  const QStringList customs = rawContact.customs();
210  for (QString custom : customs) {
211  if (custom.startsWith(QLatin1String("KADDRESSBOOK-"))) {
212  custom.remove(QStringLiteral("KADDRESSBOOK-X-"));
213  custom.remove(QStringLiteral("KADDRESSBOOK-"));
214 
215  int pos = custom.indexOf(QLatin1Char(':'));
216  QString key = custom.left(pos);
217  QString value = custom.mid(pos + 1);
218 
219  // convert anniversary correctly
220  if (key == QLatin1String("Anniversary") || key == QLatin1String("ANNIVERSARY")) {
221  const QDateTime dateTime = QDateTime::fromString(value, Qt::ISODate);
222  value = QLocale().toString(dateTime.date());
223  } else if (key == QLatin1String("BlogFeed") || key == QLatin1String("BLOGFEED")) { // blog is handled separated
224  continue;
225  } else if (blacklistedKeys.contains(key)) {
226  continue;
227  }
228 
229  // check whether we have a mapping for the title
230  const QMap<QString, QString>::ConstIterator keyIt = titleMap.constFind(key);
231  bool needToEscape = true;
232  if (keyIt != titleMap.constEnd()) {
233  key = keyIt.value();
234  } else {
235  // check whether it is a custom local field
236  for (const QVariantMap &description : customFieldDescriptions()) {
237  if (description.value(QStringLiteral("key")).toString() == key) {
238  key = description.value(QStringLiteral("title")).toString();
239  const QString descriptionType = description.value(QStringLiteral("type")).toString();
240  if (descriptionType == QLatin1String("boolean")) {
241  if (value == QLatin1String("true")) {
242  value = i18nc("Boolean value", "yes");
243  } else {
244  value = i18nc("Boolean value", "no");
245  }
246  } else if (descriptionType == QLatin1String("date")) {
247  const QDate date = QDate::fromString(value, Qt::ISODate);
248  value = QLocale().toString(date, QLocale::ShortFormat);
249  } else if (descriptionType == QLatin1String("time")) {
250  const QTime time = QTime::fromString(value, Qt::ISODate);
251  value = QLocale().toString(time);
252  } else if (descriptionType == QLatin1String("datetime")) {
253  const QDateTime dateTime = QDateTime::fromString(value, Qt::ISODate);
254  value = QLocale().toString(dateTime, QLocale::ShortFormat);
255  } else if (descriptionType == QLatin1String("url")) {
256  value = KStringHandler::tagUrls(value.toHtmlEscaped());
257  needToEscape = false;
258  }
259 
260  break;
261  }
262  }
263  }
264  if (needToEscape) {
265  value = value.toHtmlEscaped();
266  }
267  customData += rowFmtStr1.arg(key, value);
268  }
269  }
270  }
271 
272  // Assemble all parts
273  QString role = rawContact.title();
274  if (role.isEmpty()) {
275  role = rawContact.role();
276  }
277  if (role.isEmpty()) {
278  role = rawContact.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Profession"));
279  }
280 
281  QString strAddr = QStringLiteral(
282  "<div align=\"center\">"
283  "<table cellpadding=\"3\" cellspacing=\"1\">"
284  "<tr>"
285  "<td align=\"right\" valign=\"top\" width=\"30%\" rowspan=\"3\">"
286  "<img src=\"%1\" width=\"100\" vspace=\"1\"/>" // image
287  "</td>"
288  "<td colspan=\"2\" align=\"left\" width=\"70%\"><font size=\"+2\"><b>%2</b></font></td>" // name
289  "</tr>"
290  "<tr>"
291  "<td colspan=\"2\" align=\"left\" width=\"70%\">%3</td>" // role
292  "</tr>"
293  "<tr>"
294  "<td colspan=\"2\" align=\"left\" width=\"70%\">%4</td>" // organization
295  "</tr>")
296  .arg(QStringLiteral("contact_photo"))
297  .arg(rawContact.realName().toHtmlEscaped())
298  .arg(role.toHtmlEscaped())
299  .arg(rawContact.organization().toHtmlEscaped());
300 
301  strAddr.append(dynamicPart);
302  strAddr.append(notes);
303  strAddr.append(customData);
304  strAddr.append(QLatin1String("</table>"));
305 
306  if (d->displayQRcode) {
307  KConfig config(QStringLiteral("akonadi_contactrc"));
308  KConfigGroup group(&config, QStringLiteral("View"));
309  if (group.readEntry("QRCodes", true)) {
310  strAddr.append(QString::fromLatin1("<p align=\"center\">"
311  "<img src=\"%1\" vspace=\"1\"/>"
312  "</p>")
313  .arg(QStringLiteral("qrcode")));
314  }
315  }
316 
317  strAddr.append(QLatin1String("</div>\n"));
318 
319  if (form == EmbeddableForm) {
320  return strAddr;
321  }
322 
323  const QString document = QStringLiteral(
324  "<html>"
325  "<head>"
326  " <style type=\"text/css\">"
327  " a {text-decoration:none; color:%1}"
328  " </style>"
329  "</head>"
330  "<body text=\"%1\" bgcolor=\"%2\">" // text and background color
331  "%3" // contact part
332  "</body>"
333  "</html>")
334  .arg(KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name())
335  .arg(KColorScheme(QPalette::Active, KColorScheme::View).background().color().name())
336  .arg(strAddr);
337 
338  return document;
339 }
340 
341 void StandardContactFormatter::setDisplayQRCode(bool show)
342 {
343  d->displayQRcode = show;
344 }
345 
346 bool StandardContactFormatter::displayQRCode() const
347 {
348  return d->displayQRcode;
349 }
QString custom(const QString &app, const QString &name) const
QString & append(QChar ch)
bool isValid() const
QVector< QVariantMap > customFieldDescriptions() const
Returns the custom field descriptions that will be used.
HtmlForm
Describes the form of the HTML that is created.
QString toString(qlonglong i) const const
QStringList emails() const
QString organization() const
QString role() const
Creates a div HTML element that can be embedded.
QMap::const_iterator constBegin() const const
QMap::const_iterator constFind(const Key &key) const const
QTime fromString(const QString &string, Qt::DateFormat format)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QSet::iterator insert(const T &value)
T payload() const
ResourceLocatorUrl url() const
QString fullEmail(const QString &email=QString()) const
QString title() const
QDate fromString(const QString &string, Qt::DateFormat format)
QString number(int n, int base)
bool empty() const const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isEmpty() const const
QMap::const_iterator constEnd() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isValid() const const
bool isEmpty() const
QDate addYears(int nyears) const const
int indexOf(QStringView str, int from) const const
QDateTime birthday() const
KContacts::Addressee contact() const
Returns the contact that will be formatted.
QString realName() const
QString toHtmlEscaped() const const
A class that formats a contact as HTML code.
~StandardContactFormatter() override
Destroys the standard contact formatter.
QDateTime fromString(const QString &string, Qt::DateFormat format)
bool contains(const T &value) const const
const Key key(const T &value, const Key &defaultKey) const const
QString i18n(const char *text, const TYPE &arg...)
QString & replace(int position, int n, QChar after)
PhoneNumber::List phoneNumbers() const
QString typeLabel() const
StandardContactFormatter()
Creates a new standard contact formatter.
QString mid(int position, int n) const const
QDate date() const const
QString toHtml(HtmlForm form=SelfcontainedForm) const override
Returns the contact formatted as HTML.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QStringList customs() const
QDate currentDate()
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
bool isEmpty() const const
QString left(int n) const const
QString fromLatin1(const char *str, int size)
static QString birthdayLabel()
QMap::iterator insert(const Key &key, const T &value)
bool isEmpty() const const
Akonadi::Item item() const
Returns the item who&#39;s payload will be formatted.
Address::List addresses() const
bool hasPayload() const
int year() const const
T readEntry(const QString &key, const T &aDefault) const
QString note() const
const T value(const Key &key, const T &defaultValue) const const
KCOREADDONS_EXPORT QString tagUrls(const QString &text)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Wed Jun 23 2021 23:09:25 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.