File indexing completed on 2024-11-24 04:44:14

0001 /*
0002     This file is part of libkabc and/or kaddressbook.
0003     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB <info@klaralvdalens-datakonsult.se>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "contact.h"
0009 #include "pimkolab_debug.h"
0010 #include <KContacts/Addressee>
0011 #include <QFile>
0012 #include <cfloat>
0013 
0014 using namespace KolabV2;
0015 
0016 namespace
0017 {
0018 inline QString defaultPictureAttachmentName()
0019 {
0020     return QStringLiteral("kolab-picture.png");
0021 }
0022 
0023 inline QString defaultLogoAttachmentName()
0024 {
0025     return QStringLiteral("kolab-logo.png");
0026 }
0027 
0028 inline QString defaultSoundAttachmentName()
0029 {
0030     return QStringLiteral("sound");
0031 }
0032 
0033 inline QString unhandledTagAppName()
0034 {
0035     return QStringLiteral("KOLABUNHANDLED");
0036 } // no hyphens in appnames!
0037 }
0038 
0039 // saving (addressee->xml)
0040 Contact::Contact(const KContacts::Addressee *addr)
0041     : mHasGeo(false)
0042 {
0043     setFields(addr);
0044 }
0045 
0046 // loading (xml->addressee)
0047 Contact::Contact(const QString &xml)
0048 {
0049     load(xml);
0050 }
0051 
0052 Contact::~Contact() = default;
0053 
0054 void Contact::setGivenName(const QString &name)
0055 {
0056     mGivenName = name;
0057 }
0058 
0059 QString Contact::givenName() const
0060 {
0061     return mGivenName;
0062 }
0063 
0064 void Contact::setMiddleNames(const QString &names)
0065 {
0066     mMiddleNames = names;
0067 }
0068 
0069 QString Contact::middleNames() const
0070 {
0071     return mMiddleNames;
0072 }
0073 
0074 void Contact::setLastName(const QString &name)
0075 {
0076     mLastName = name;
0077 }
0078 
0079 QString Contact::lastName() const
0080 {
0081     return mLastName;
0082 }
0083 
0084 void Contact::setFullName(const QString &name)
0085 {
0086     mFullName = name;
0087 }
0088 
0089 QString Contact::fullName() const
0090 {
0091     return mFullName;
0092 }
0093 
0094 void Contact::setInitials(const QString &initials)
0095 {
0096     mInitials = initials;
0097 }
0098 
0099 QString Contact::initials() const
0100 {
0101     return mInitials;
0102 }
0103 
0104 void Contact::setPrefix(const QString &prefix)
0105 {
0106     mPrefix = prefix;
0107 }
0108 
0109 QString Contact::prefix() const
0110 {
0111     return mPrefix;
0112 }
0113 
0114 void Contact::setSuffix(const QString &suffix)
0115 {
0116     mSuffix = suffix;
0117 }
0118 
0119 QString Contact::suffix() const
0120 {
0121     return mSuffix;
0122 }
0123 
0124 void Contact::setRole(const QString &role)
0125 {
0126     mRole = role;
0127 }
0128 
0129 QString Contact::role() const
0130 {
0131     return mRole;
0132 }
0133 
0134 void Contact::setFreeBusyUrl(const QString &fbUrl)
0135 {
0136     mFreeBusyUrl = fbUrl;
0137 }
0138 
0139 QString Contact::freeBusyUrl() const
0140 {
0141     return mFreeBusyUrl;
0142 }
0143 
0144 void Contact::setOrganization(const QString &organization)
0145 {
0146     mOrganization = organization;
0147 }
0148 
0149 QString Contact::organization() const
0150 {
0151     return mOrganization;
0152 }
0153 
0154 void Contact::setWebPage(const QString &url)
0155 {
0156     mWebPage = url;
0157 }
0158 
0159 QString Contact::webPage() const
0160 {
0161     return mWebPage;
0162 }
0163 
0164 void Contact::setIMAddress(const QString &imAddress)
0165 {
0166     mIMAddress = imAddress;
0167 }
0168 
0169 QString Contact::imAddress() const
0170 {
0171     return mIMAddress;
0172 }
0173 
0174 void Contact::setDepartment(const QString &department)
0175 {
0176     mDepartment = department;
0177 }
0178 
0179 QString Contact::department() const
0180 {
0181     return mDepartment;
0182 }
0183 
0184 void Contact::setOfficeLocation(const QString &location)
0185 {
0186     mOfficeLocation = location;
0187 }
0188 
0189 QString Contact::officeLocation() const
0190 {
0191     return mOfficeLocation;
0192 }
0193 
0194 void Contact::setProfession(const QString &profession)
0195 {
0196     mProfession = profession;
0197 }
0198 
0199 QString Contact::profession() const
0200 {
0201     return mProfession;
0202 }
0203 
0204 void Contact::setTitle(const QString &title)
0205 {
0206     mTitle = title;
0207 }
0208 
0209 QString Contact::title() const
0210 {
0211     return mTitle;
0212 }
0213 
0214 void Contact::setManagerName(const QString &name)
0215 {
0216     mManagerName = name;
0217 }
0218 
0219 QString Contact::managerName() const
0220 {
0221     return mManagerName;
0222 }
0223 
0224 void Contact::setAssistant(const QString &name)
0225 {
0226     mAssistant = name;
0227 }
0228 
0229 QString Contact::assistant() const
0230 {
0231     return mAssistant;
0232 }
0233 
0234 void Contact::setNickName(const QString &name)
0235 {
0236     mNickName = name;
0237 }
0238 
0239 QString Contact::nickName() const
0240 {
0241     return mNickName;
0242 }
0243 
0244 void Contact::setSpouseName(const QString &name)
0245 {
0246     mSpouseName = name;
0247 }
0248 
0249 QString Contact::spouseName() const
0250 {
0251     return mSpouseName;
0252 }
0253 
0254 void Contact::setBirthday(QDate date)
0255 {
0256     mBirthday = date;
0257 }
0258 
0259 QDate Contact::birthday() const
0260 {
0261     return mBirthday;
0262 }
0263 
0264 void Contact::setAnniversary(QDate date)
0265 {
0266     mAnniversary = date;
0267 }
0268 
0269 QDate Contact::anniversary() const
0270 {
0271     return mAnniversary;
0272 }
0273 
0274 void Contact::setChildren(const QString &children)
0275 {
0276     mChildren = children;
0277 }
0278 
0279 QString Contact::children() const
0280 {
0281     return mChildren;
0282 }
0283 
0284 void Contact::setGender(const QString &gender)
0285 {
0286     mGender = gender;
0287 }
0288 
0289 QString Contact::gender() const
0290 {
0291     return mGender;
0292 }
0293 
0294 void Contact::setLanguage(const QString &language)
0295 {
0296     mLanguage = language;
0297 }
0298 
0299 QString Contact::language() const
0300 {
0301     return mLanguage;
0302 }
0303 
0304 void Contact::addPhoneNumber(const PhoneNumber &number)
0305 {
0306     mPhoneNumbers.append(number);
0307 }
0308 
0309 QList<Contact::PhoneNumber> &Contact::phoneNumbers()
0310 {
0311     return mPhoneNumbers;
0312 }
0313 
0314 const QList<Contact::PhoneNumber> &Contact::phoneNumbers() const
0315 {
0316     return mPhoneNumbers;
0317 }
0318 
0319 void Contact::addEmail(const Email &email)
0320 {
0321     mEmails.append(email);
0322 }
0323 
0324 QList<Contact::Email> &Contact::emails()
0325 {
0326     return mEmails;
0327 }
0328 
0329 QString Contact::fullEmail() const
0330 {
0331     return mFullEmail;
0332 }
0333 
0334 const QList<Contact::Email> &Contact::emails() const
0335 {
0336     return mEmails;
0337 }
0338 
0339 void Contact::addAddress(const Contact::Address &address)
0340 {
0341     mAddresses.append(address);
0342 }
0343 
0344 QList<Contact::Address> &Contact::addresses()
0345 {
0346     return mAddresses;
0347 }
0348 
0349 const QList<Contact::Address> &Contact::addresses() const
0350 {
0351     return mAddresses;
0352 }
0353 
0354 void Contact::setPreferredAddress(const QString &address)
0355 {
0356     mPreferredAddress = address;
0357 }
0358 
0359 QString Contact::preferredAddress() const
0360 {
0361     return mPreferredAddress;
0362 }
0363 
0364 bool Contact::loadNameAttribute(QDomElement &element)
0365 {
0366     for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
0367         if (n.isComment()) {
0368             continue;
0369         }
0370         if (n.isElement()) {
0371             QDomElement e = n.toElement();
0372             QString tagName = e.tagName();
0373 
0374             if (tagName == QLatin1StringView("given-name")) {
0375                 setGivenName(e.text());
0376             } else if (tagName == QLatin1StringView("middle-names")) {
0377                 setMiddleNames(e.text());
0378             } else if (tagName == QLatin1StringView("last-name")) {
0379                 setLastName(e.text());
0380             } else if (tagName == QLatin1StringView("full-name")) {
0381                 setFullName(e.text());
0382             } else if (tagName == QLatin1StringView("initials")) {
0383                 setInitials(e.text());
0384             } else if (tagName == QLatin1StringView("prefix")) {
0385                 setPrefix(e.text());
0386             } else if (tagName == QLatin1StringView("suffix")) {
0387                 setSuffix(e.text());
0388             } else {
0389                 // TODO: Unhandled tag - save for later storage
0390                 qCDebug(PIMKOLAB_LOG) << "Warning: Unhandled tag" << e.tagName();
0391             }
0392         } else {
0393             qCDebug(PIMKOLAB_LOG) << "Node is not a comment or an element???";
0394         }
0395     }
0396 
0397     return true;
0398 }
0399 
0400 void Contact::saveNameAttribute(QDomElement &element) const
0401 {
0402     QDomElement e = element.ownerDocument().createElement(QStringLiteral("name"));
0403     element.appendChild(e);
0404 
0405     writeString(e, QStringLiteral("given-name"), givenName());
0406     writeString(e, QStringLiteral("middle-names"), middleNames());
0407     writeString(e, QStringLiteral("last-name"), lastName());
0408     writeString(e, QStringLiteral("full-name"), fullName());
0409     writeString(e, QStringLiteral("initials"), initials());
0410     writeString(e, QStringLiteral("prefix"), prefix());
0411     writeString(e, QStringLiteral("suffix"), suffix());
0412 }
0413 
0414 bool Contact::loadPhoneAttribute(QDomElement &element)
0415 {
0416     PhoneNumber number;
0417     for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
0418         if (n.isComment()) {
0419             continue;
0420         }
0421         if (n.isElement()) {
0422             QDomElement e = n.toElement();
0423             QString tagName = e.tagName();
0424 
0425             if (tagName == QLatin1StringView("type")) {
0426                 number.type = e.text();
0427             } else if (tagName == QLatin1StringView("number")) {
0428                 number.number = e.text();
0429             } else {
0430                 // TODO: Unhandled tag - save for later storage
0431                 qCDebug(PIMKOLAB_LOG) << "Warning: Unhandled tag" << e.tagName();
0432             }
0433         } else {
0434             qCDebug(PIMKOLAB_LOG) << "Node is not a comment or an element???";
0435         }
0436     }
0437 
0438     addPhoneNumber(number);
0439     return true;
0440 }
0441 
0442 void Contact::savePhoneAttributes(QDomElement &element) const
0443 {
0444     QList<PhoneNumber>::ConstIterator it = mPhoneNumbers.constBegin();
0445     const QList<PhoneNumber>::ConstIterator end = mPhoneNumbers.constEnd();
0446     for (; it != end; ++it) {
0447         QDomElement e = element.ownerDocument().createElement(QStringLiteral("phone"));
0448         element.appendChild(e);
0449         const PhoneNumber &p = *it;
0450         writeString(e, QStringLiteral("type"), p.type);
0451         writeString(e, QStringLiteral("number"), p.number);
0452     }
0453 }
0454 
0455 void Contact::saveEmailAttributes(QDomElement &element) const
0456 {
0457     QList<Email>::ConstIterator it = mEmails.constBegin();
0458     QList<Email>::ConstIterator end = mEmails.constEnd();
0459     for (; it != end; ++it) {
0460         saveEmailAttribute(element, *it);
0461     }
0462 }
0463 
0464 void Contact::loadCustomAttributes(QDomElement &element)
0465 {
0466     Custom custom;
0467     custom.app = element.attribute(QStringLiteral("app"));
0468     custom.name = element.attribute(QStringLiteral("name"));
0469     custom.value = element.attribute(QStringLiteral("value"));
0470     mCustomList.append(custom);
0471 }
0472 
0473 void Contact::saveCustomAttributes(QDomElement &element) const
0474 {
0475     QList<Custom>::ConstIterator it = mCustomList.constBegin();
0476     const QList<Custom>::ConstIterator total = mCustomList.constEnd();
0477     for (; it != total; ++it) {
0478         Q_ASSERT(!(*it).name.isEmpty());
0479         if ((*it).app == unhandledTagAppName()) {
0480             writeString(element, (*it).name, (*it).value);
0481         } else {
0482             // Let's use attributes so that other tag-preserving-code doesn't need sub-elements
0483             QDomElement e = element.ownerDocument().createElement(QStringLiteral("x-custom"));
0484             element.appendChild(e);
0485             e.setAttribute(QStringLiteral("app"), (*it).app);
0486             e.setAttribute(QStringLiteral("name"), (*it).name);
0487             e.setAttribute(QStringLiteral("value"), (*it).value);
0488         }
0489     }
0490 }
0491 
0492 bool Contact::loadAddressAttribute(QDomElement &element)
0493 {
0494     Address address;
0495 
0496     for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
0497         if (n.isComment()) {
0498             continue;
0499         }
0500         if (n.isElement()) {
0501             QDomElement e = n.toElement();
0502             QString tagName = e.tagName();
0503 
0504             if (tagName == QLatin1StringView("type")) {
0505                 address.type = e.text();
0506             } else if (tagName == QLatin1StringView("x-kde-type")) {
0507                 address.kdeAddressType = e.text().toInt();
0508             } else if (tagName == QLatin1StringView("street")) {
0509                 address.street = e.text();
0510             } else if (tagName == QLatin1StringView("pobox")) {
0511                 address.pobox = e.text();
0512             } else if (tagName == QLatin1StringView("locality")) {
0513                 address.locality = e.text();
0514             } else if (tagName == QLatin1StringView("region")) {
0515                 address.region = e.text();
0516             } else if (tagName == QLatin1StringView("postal-code")) {
0517                 address.postalCode = e.text();
0518             } else if (tagName == QLatin1StringView("country")) {
0519                 address.country = e.text();
0520             } else {
0521                 // TODO: Unhandled tag - save for later storage
0522                 qCDebug(PIMKOLAB_LOG) << "Warning: Unhandled tag" << e.tagName();
0523             }
0524         } else {
0525             qCDebug(PIMKOLAB_LOG) << "Node is not a comment or an element???";
0526         }
0527     }
0528 
0529     addAddress(address);
0530     return true;
0531 }
0532 
0533 void Contact::saveAddressAttributes(QDomElement &element) const
0534 {
0535     QList<Address>::ConstIterator it = mAddresses.constBegin();
0536     const QList<Address>::ConstIterator end = mAddresses.constEnd();
0537     for (; it != end; ++it) {
0538         QDomElement e = element.ownerDocument().createElement(QStringLiteral("address"));
0539         element.appendChild(e);
0540         const Address &a = *it;
0541         writeString(e, QStringLiteral("type"), a.type);
0542         writeString(e, QStringLiteral("x-kde-type"), QString::number(a.kdeAddressType));
0543         if (!a.street.isEmpty()) {
0544             writeString(e, QStringLiteral("street"), a.street);
0545         }
0546         if (!a.pobox.isEmpty()) {
0547             writeString(e, QStringLiteral("pobox"), a.pobox);
0548         }
0549         if (!a.locality.isEmpty()) {
0550             writeString(e, QStringLiteral("locality"), a.locality);
0551         }
0552         if (!a.region.isEmpty()) {
0553             writeString(e, QStringLiteral("region"), a.region);
0554         }
0555         if (!a.postalCode.isEmpty()) {
0556             writeString(e, QStringLiteral("postal-code"), a.postalCode);
0557         }
0558         if (!a.country.isEmpty()) {
0559             writeString(e, QStringLiteral("country"), a.country);
0560         }
0561     }
0562 }
0563 
0564 bool Contact::loadAttribute(QDomElement &element)
0565 {
0566     const QString tagName = element.tagName();
0567     switch (tagName[0].toLatin1()) {
0568     case 'a':
0569         if (tagName == QLatin1StringView("address")) {
0570             return loadAddressAttribute(element);
0571         }
0572         if (tagName == QLatin1StringView("assistant")) {
0573             setAssistant(element.text());
0574             return true;
0575         }
0576         if (tagName == QLatin1StringView("anniversary")) {
0577             if (!element.text().isEmpty()) {
0578                 setAnniversary(stringToDate(element.text()));
0579             }
0580             return true;
0581         }
0582         break;
0583     case 'b':
0584         if (tagName == QLatin1StringView("birthday")) {
0585             if (!element.text().isEmpty()) {
0586                 setBirthday(stringToDate(element.text()));
0587             }
0588             return true;
0589         }
0590         break;
0591     case 'c':
0592         if (tagName == QLatin1StringView("children")) {
0593             setChildren(element.text());
0594             return true;
0595         }
0596         break;
0597     case 'd':
0598         if (tagName == QLatin1StringView("department")) {
0599             setDepartment(element.text());
0600             return true;
0601         }
0602         break;
0603     case 'e':
0604         if (tagName == QLatin1StringView("email")) {
0605             Email email;
0606             if (loadEmailAttribute(element, email)) {
0607                 addEmail(email);
0608                 return true;
0609             } else {
0610                 return false;
0611             }
0612         }
0613         break;
0614     case 'f':
0615         if (tagName == QLatin1StringView("free-busy-url")) {
0616             setFreeBusyUrl(element.text());
0617             return true;
0618         }
0619         break;
0620     case 'g':
0621         if (tagName == QLatin1StringView("gender")) {
0622             setGender(element.text());
0623             return true;
0624         }
0625         break;
0626     case 'i':
0627         if (tagName == QLatin1StringView("im-address")) {
0628             setIMAddress(element.text());
0629             return true;
0630         }
0631         break;
0632     case 'j':
0633         if (tagName == QLatin1StringView("job-title")) {
0634             // see saveAttributes: <job-title> is mapped to the Role field
0635             setTitle(element.text());
0636             return true;
0637         }
0638         break;
0639     case 'l':
0640         if (tagName == QLatin1StringView("language")) {
0641             setLanguage(element.text());
0642             return true;
0643         }
0644         if (tagName == QLatin1StringView("latitude")) {
0645             setLatitude(element.text().toFloat());
0646             mHasGeo = true;
0647             return true;
0648         }
0649         if (tagName == QLatin1StringView("longitude")) {
0650             setLongitude(element.text().toFloat());
0651             mHasGeo = true;
0652         }
0653         break;
0654     case 'm':
0655         if (tagName == QLatin1StringView("manager-name")) {
0656             setManagerName(element.text());
0657             return true;
0658         }
0659         break;
0660     case 'n':
0661         if (tagName == QLatin1StringView("name")) {
0662             return loadNameAttribute(element);
0663         }
0664         if (tagName == QLatin1StringView("nick-name")) {
0665             setNickName(element.text());
0666             return true;
0667         }
0668         break;
0669     case 'o':
0670         if (tagName == QLatin1StringView("organization")) {
0671             setOrganization(element.text());
0672             return true;
0673         }
0674         if (tagName == QLatin1StringView("office-location")) {
0675             setOfficeLocation(element.text());
0676             return true;
0677         }
0678         break;
0679     case 'p':
0680         if (tagName == QLatin1StringView("profession")) {
0681             setProfession(element.text());
0682             return true;
0683         }
0684         if (tagName == QLatin1StringView("picture")) {
0685             mPictureAttachmentName = element.text();
0686             return true;
0687         }
0688         if (tagName == QLatin1StringView("phone")) {
0689             return loadPhoneAttribute(element);
0690         }
0691         if (tagName == QLatin1StringView("preferred-address")) {
0692             setPreferredAddress(element.text());
0693             return true;
0694         }
0695         break;
0696     case 'r':
0697         if (tagName == QLatin1StringView("role")) {
0698             setRole(element.text());
0699             return true;
0700         }
0701         break;
0702     case 's':
0703         if (tagName == QLatin1StringView("spouse-name")) {
0704             setSpouseName(element.text());
0705             return true;
0706         }
0707         break;
0708     case 'x':
0709         if (tagName == QLatin1StringView("x-logo")) {
0710             mLogoAttachmentName = element.text();
0711             return true;
0712         }
0713         if (tagName == QLatin1StringView("x-sound")) {
0714             mSoundAttachmentName = element.text();
0715             return true;
0716         }
0717         if (tagName == QLatin1StringView("x-custom")) {
0718             loadCustomAttributes(element);
0719             return true;
0720         }
0721         if (tagName == QLatin1StringView("x-title")) {
0722             setTitle(element.text());
0723             return true;
0724         }
0725         break;
0726     case 'w':
0727         if (tagName == QLatin1StringView("web-page")) {
0728             setWebPage(element.text());
0729             return true;
0730         }
0731         break;
0732     default:
0733         break;
0734     }
0735     return KolabBase::loadAttribute(element);
0736 }
0737 
0738 bool Contact::saveAttributes(QDomElement &element) const
0739 {
0740     // Save the base class elements
0741     KolabBase::saveAttributes(element);
0742     saveNameAttribute(element);
0743     writeString(element, QStringLiteral("free-busy-url"), freeBusyUrl());
0744     writeString(element, QStringLiteral("organization"), organization());
0745     writeString(element, QStringLiteral("web-page"), webPage());
0746     writeString(element, QStringLiteral("im-address"), imAddress());
0747     writeString(element, QStringLiteral("department"), department());
0748     writeString(element, QStringLiteral("office-location"), officeLocation());
0749     writeString(element, QStringLiteral("profession"), profession());
0750     writeString(element, QStringLiteral("role"), role());
0751     writeString(element, QStringLiteral("job-title"), title());
0752     writeString(element, QStringLiteral("manager-name"), managerName());
0753     writeString(element, QStringLiteral("assistant"), assistant());
0754     writeString(element, QStringLiteral("nick-name"), nickName());
0755     writeString(element, QStringLiteral("spouse-name"), spouseName());
0756     writeString(element, QStringLiteral("birthday"), dateToString(birthday()));
0757     writeString(element, QStringLiteral("anniversary"), dateToString(anniversary()));
0758     if (!picture().isNull()) {
0759         writeString(element, QStringLiteral("picture"), mPictureAttachmentName);
0760     }
0761     if (!logo().isNull()) {
0762         writeString(element, QStringLiteral("x-logo"), mLogoAttachmentName);
0763     }
0764     if (!sound().isNull()) {
0765         writeString(element, QStringLiteral("x-sound"), mSoundAttachmentName);
0766     }
0767     writeString(element, QStringLiteral("children"), children());
0768     writeString(element, QStringLiteral("gender"), gender());
0769     writeString(element, QStringLiteral("language"), language());
0770     savePhoneAttributes(element);
0771     saveEmailAttributes(element);
0772     saveAddressAttributes(element);
0773     writeString(element, QStringLiteral("preferred-address"), preferredAddress());
0774     if (mHasGeo) {
0775         writeString(element, QStringLiteral("latitude"), QString::number(latitude(), 'g', DBL_DIG));
0776         writeString(element, QStringLiteral("longitude"), QString::number(longitude(), 'g', DBL_DIG));
0777     }
0778     saveCustomAttributes(element);
0779 
0780     return true;
0781 }
0782 
0783 bool Contact::loadXML(const QDomDocument &document)
0784 {
0785     QDomElement top = document.documentElement();
0786 
0787     if (top.tagName() != QLatin1StringView("contact")) {
0788         qCWarning(PIMKOLAB_LOG) << QStringLiteral("XML error: Top tag was %1 instead of the expected contact").arg(top.tagName());
0789         return false;
0790     }
0791 
0792     for (QDomNode n = top.firstChild(); !n.isNull(); n = n.nextSibling()) {
0793         if (n.isComment()) {
0794             continue;
0795         }
0796         if (n.isElement()) {
0797             QDomElement e = n.toElement();
0798             if (!loadAttribute(e)) {
0799                 // Unhandled tag - save for later storage
0800                 // qCDebug(PIMKOLAB_LOG) <<"Saving unhandled tag" << e.tagName();
0801                 Custom c;
0802                 c.app = unhandledTagAppName();
0803                 c.name = e.tagName();
0804                 c.value = e.text();
0805                 mCustomList.append(c);
0806             }
0807         } else {
0808             qCDebug(PIMKOLAB_LOG) << "Node is not a comment or an element???";
0809         }
0810     }
0811 
0812     return true;
0813 }
0814 
0815 QString Contact::saveXML() const
0816 {
0817     QDomDocument document = domTree();
0818     QDomElement element = document.createElement(QStringLiteral("contact"));
0819     element.setAttribute(QStringLiteral("version"), QStringLiteral("1.0"));
0820     saveAttributes(element);
0821     document.appendChild(element);
0822     return document.toString();
0823 }
0824 
0825 static QString addressTypeToString(int /*KContacts::Address::Type*/ type)
0826 {
0827     if (type & KContacts::Address::Home) {
0828         return QStringLiteral("home");
0829     }
0830     if (type & KContacts::Address::Work) {
0831         return QStringLiteral("business");
0832     }
0833     return QStringLiteral("other");
0834 }
0835 
0836 static int addressTypeFromString(const QString &type)
0837 {
0838     if (type == QLatin1StringView("home")) {
0839         return KContacts::Address::Home;
0840     }
0841     if (type == QLatin1StringView("business")) {
0842         return KContacts::Address::Work;
0843     }
0844     // well, this shows "other" in the editor, which is what we want...
0845     return KContacts::Address::Dom | KContacts::Address::Intl | KContacts::Address::Postal | KContacts::Address::Parcel;
0846 }
0847 
0848 static QStringList phoneTypeToString(KContacts::PhoneNumber::Type type)
0849 {
0850     // KABC has a bitfield, i.e. the same phone number can be used for work and home
0851     // and fax and cellphone etc. etc.
0852     // So when saving we need to create as many tags as bits that were set.
0853     QStringList types;
0854     if (type & KContacts::PhoneNumber::Fax) {
0855         if (type & KContacts::PhoneNumber::Home) {
0856             types << QStringLiteral("homefax");
0857         } else { // assume work -- if ( type & KContacts::PhoneNumber::Work )
0858             types << QStringLiteral("businessfax");
0859         }
0860         type = type & ~KContacts::PhoneNumber::Home;
0861         type = type & ~KContacts::PhoneNumber::Work;
0862     }
0863 
0864     // To support both "home1" and "home2", map Home+Pref to home1
0865     if ((type & KContacts::PhoneNumber::Home) && (type & KContacts::PhoneNumber::Pref)) {
0866         types << QStringLiteral("home1");
0867         type = type & ~KContacts::PhoneNumber::Home;
0868         type = type & ~KContacts::PhoneNumber::Pref;
0869     }
0870     // To support both "business1" and "business2", map Work+Pref to business1
0871     if ((type & KContacts::PhoneNumber::Work) && (type & KContacts::PhoneNumber::Pref)) {
0872         types << QStringLiteral("business1");
0873         type = type & ~KContacts::PhoneNumber::Work;
0874         type = type & ~KContacts::PhoneNumber::Pref;
0875     }
0876 
0877     if (type & KContacts::PhoneNumber::Home) {
0878         types << QStringLiteral("home2");
0879     }
0880     if (type & KContacts::PhoneNumber::Msg) { // Msg==messaging
0881         types << QStringLiteral("company");
0882     }
0883     if (type & KContacts::PhoneNumber::Work) {
0884         types << QStringLiteral("business2");
0885     }
0886     if (type & KContacts::PhoneNumber::Pref) {
0887         types << QStringLiteral("primary");
0888     }
0889     if (type & KContacts::PhoneNumber::Voice) {
0890         types << QStringLiteral("callback"); // ##
0891     }
0892     if (type & KContacts::PhoneNumber::Cell) {
0893         types << QStringLiteral("mobile");
0894     }
0895     if (type & KContacts::PhoneNumber::Video) {
0896         types << QStringLiteral("radio"); // ##
0897     }
0898     if (type & KContacts::PhoneNumber::Bbs) {
0899         types << QStringLiteral("ttytdd");
0900     }
0901     if (type & KContacts::PhoneNumber::Modem) {
0902         types << QStringLiteral("telex"); // #
0903     }
0904     if (type & KContacts::PhoneNumber::Car) {
0905         types << QStringLiteral("car");
0906     }
0907     if (type & KContacts::PhoneNumber::Isdn) {
0908         types << QStringLiteral("isdn");
0909     }
0910     if (type & KContacts::PhoneNumber::Pcs) {
0911         types << QStringLiteral("assistant"); // ## Assistant is e.g. secretary
0912     }
0913     if (type & KContacts::PhoneNumber::Pager) {
0914         types << QStringLiteral("pager");
0915     }
0916     return types;
0917 }
0918 
0919 static KContacts::PhoneNumber::Type phoneTypeFromString(const QString &type)
0920 {
0921     if (type == QLatin1StringView("homefax")) {
0922         return KContacts::PhoneNumber::Home | KContacts::PhoneNumber::Fax;
0923     }
0924     if (type == QLatin1StringView("businessfax")) {
0925         return KContacts::PhoneNumber::Work | KContacts::PhoneNumber::Fax;
0926     }
0927     if (type == QLatin1StringView("business1")) {
0928         return KContacts::PhoneNumber::Work | KContacts::PhoneNumber::Pref;
0929     }
0930     if (type == QLatin1StringView("business2")) {
0931         return KContacts::PhoneNumber::Work;
0932     }
0933     if (type == QLatin1StringView("home1")) {
0934         return KContacts::PhoneNumber::Home | KContacts::PhoneNumber::Pref;
0935     }
0936     if (type == QLatin1StringView("home2")) {
0937         return KContacts::PhoneNumber::Home;
0938     }
0939     if (type == QLatin1StringView("company")) {
0940         return KContacts::PhoneNumber::Msg;
0941     }
0942     if (type == QLatin1StringView("primary")) {
0943         return KContacts::PhoneNumber::Pref;
0944     }
0945     if (type == QLatin1StringView("callback")) {
0946         return KContacts::PhoneNumber::Voice;
0947     }
0948     if (type == QLatin1StringView("mobile")) {
0949         return KContacts::PhoneNumber::Cell;
0950     }
0951     if (type == QLatin1StringView("radio")) {
0952         return KContacts::PhoneNumber::Video;
0953     }
0954     if (type == QLatin1StringView("ttytdd")) {
0955         return KContacts::PhoneNumber::Bbs;
0956     }
0957     if (type == QLatin1StringView("telex")) {
0958         return KContacts::PhoneNumber::Modem;
0959     }
0960     if (type == QLatin1StringView("car")) {
0961         return KContacts::PhoneNumber::Car;
0962     }
0963     if (type == QLatin1StringView("isdn")) {
0964         return KContacts::PhoneNumber::Isdn;
0965     }
0966     if (type == QLatin1StringView("assistant")) {
0967         return KContacts::PhoneNumber::Pcs;
0968     }
0969     if (type == QLatin1StringView("pager")) {
0970         return KContacts::PhoneNumber::Pager;
0971     }
0972     return KContacts::PhoneNumber::Home; // whatever
0973 }
0974 
0975 static const char *s_knownCustomFields[] =
0976     {"X-IMAddress", "X-Office", "X-Profession", "X-ManagersName", "X-AssistantsName", "X-SpousesName", "X-Anniversary", "DistributionList", nullptr};
0977 
0978 // The saving is addressee -> Contact -> xml, this is the first part
0979 void Contact::setFields(const KContacts::Addressee *addressee)
0980 {
0981     KolabBase::setFields(addressee);
0982 
0983     setGivenName(addressee->givenName());
0984     setMiddleNames(addressee->additionalName());
0985     setLastName(addressee->familyName());
0986     setFullName(addressee->formattedName());
0987     setPrefix(addressee->prefix());
0988     setSuffix(addressee->suffix());
0989     setOrganization(addressee->organization());
0990     setWebPage(addressee->url().url().url());
0991     setIMAddress(addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-IMAddress")));
0992     setDepartment(addressee->department());
0993     setOfficeLocation(addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Office")));
0994     setProfession(addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Profession")));
0995     setRole(addressee->role());
0996     setTitle(addressee->title());
0997     setManagerName(addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-ManagersName")));
0998     setAssistant(addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-AssistantsName")));
0999     setNickName(addressee->nickName());
1000     setSpouseName(addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-SpousesName")));
1001     if (!addressee->birthday().isNull()) {
1002         setBirthday(addressee->birthday().date());
1003     }
1004     const QString &anniversary = addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Anniversary"));
1005     if (!anniversary.isEmpty()) {
1006         setAnniversary(stringToDate(anniversary));
1007     }
1008 
1009     const QStringList emails = addressee->emails();
1010     // Conversion problem here:
1011     // KContacts::Addressee has only one full name and N addresses, but the XML format
1012     // has N times (fullname+address). So we just copy the fullname over and ignore it on loading.
1013     for (QStringList::ConstIterator it = emails.constBegin(), end = emails.constEnd(); it != end; ++it) {
1014         Email email;
1015         email.displayName = fullName();
1016         email.smtpAddress = *it;
1017         addEmail(email);
1018     }
1019 
1020     // save formatted full email for later usage
1021     mFullEmail = addressee->fullEmail();
1022 
1023     // Now the real-world addresses
1024     QString preferredAddress = QStringLiteral("home");
1025     const KContacts::Address::List addresses = addressee->addresses();
1026     for (KContacts::Address::List::ConstIterator it = addresses.constBegin(), end = addresses.constEnd(); it != end; ++it) {
1027         Address address;
1028         address.kdeAddressType = (*it).type();
1029         address.type = addressTypeToString(address.kdeAddressType);
1030         address.street = (*it).street();
1031         address.pobox = (*it).postOfficeBox();
1032         address.locality = (*it).locality();
1033         address.region = (*it).region();
1034         address.postalCode = (*it).postalCode();
1035         address.country = (*it).country();
1036         // ## TODO not in the XML format: extended address info.
1037         // ## KDE-specific tags? Or hiding those fields? Or adding a warning?
1038         addAddress(address);
1039         if (address.kdeAddressType & KContacts::Address::Pref) {
1040             preferredAddress = address.type; // home, business or other
1041         }
1042     }
1043     setPreferredAddress(preferredAddress);
1044 
1045     const KContacts::PhoneNumber::List phones = addressee->phoneNumbers();
1046     for (KContacts::PhoneNumber::List::ConstIterator it = phones.constBegin(), endIt = phones.constEnd(); it != endIt; ++it) {
1047         // Create a tag per phone type set in the bitfield
1048         QStringList types = phoneTypeToString((*it).type());
1049         for (QStringList::ConstIterator typit = types.constBegin(), end = types.constEnd(); typit != end; ++typit) {
1050             PhoneNumber phoneNumber;
1051             phoneNumber.type = *typit;
1052             phoneNumber.number = (*it).number();
1053             addPhoneNumber(phoneNumber);
1054         }
1055     }
1056 
1057     setPicture(loadPictureFromAddressee(addressee->photo()), addressee->photo().type());
1058     mPictureAttachmentName = addressee->custom(QStringLiteral("KOLAB"), QStringLiteral("PictureAttachmentName"));
1059     if (mPictureAttachmentName.isEmpty()) {
1060         mPictureAttachmentName = defaultPictureAttachmentName();
1061     }
1062 
1063     setLogo(loadPictureFromAddressee(addressee->logo()), addressee->logo().type());
1064     mLogoAttachmentName = addressee->custom(QStringLiteral("KOLAB"), QStringLiteral("LogoAttachmentName"));
1065     if (mLogoAttachmentName.isEmpty()) {
1066         mLogoAttachmentName = defaultLogoAttachmentName();
1067     }
1068 
1069     setSound(loadSoundFromAddressee(addressee->sound()));
1070     mSoundAttachmentName = addressee->custom(QStringLiteral("KOLAB"), QStringLiteral("SoundAttachmentName"));
1071     if (mSoundAttachmentName.isEmpty()) {
1072         mSoundAttachmentName = defaultSoundAttachmentName();
1073     }
1074 
1075     if (addressee->geo().isValid()) {
1076         setLatitude(addressee->geo().latitude());
1077         setLongitude(addressee->geo().longitude());
1078         mHasGeo = true;
1079     }
1080 
1081     // Other KADDRESSBOOK custom fields than those already handled
1082     //    (includes e.g. crypto settings, and extra im addresses)
1083     QStringList knownCustoms;
1084     for (const char **p = s_knownCustomFields; *p; ++p) {
1085         knownCustoms << QString::fromLatin1(*p);
1086     }
1087     const QStringList customs = addressee->customs();
1088     for (QStringList::ConstIterator it = customs.constBegin(), end = customs.constEnd(); it != end; ++it) {
1089         // KContacts::Addressee doesn't offer a real way to iterate over customs, other than splitting strings ourselves
1090         // The format is "app-name:value".
1091         int pos = (*it).indexOf(QLatin1Char('-'));
1092         if (pos == -1) {
1093             continue;
1094         }
1095         QString app = (*it).left(pos);
1096         if (app == QLatin1StringView("KOLAB")) {
1097             continue;
1098         }
1099         QString name = (*it).mid(pos + 1);
1100         pos = name.indexOf(QLatin1Char(':'));
1101         if (pos == -1) {
1102             continue;
1103         }
1104         QString value = name.mid(pos + 1);
1105         name.truncate(pos);
1106         if (!knownCustoms.contains(name)) {
1107             // qCDebug(PIMKOLAB_LOG) <<"app=" << app <<" name=" << name <<" value=" << value;
1108             Custom c;
1109             if (app != QLatin1StringView("KADDRESSBOOK")) { // that's the default
1110                 c.app = app;
1111             }
1112             c.name = name;
1113             c.value = value;
1114             mCustomList.append(c);
1115         }
1116     }
1117 
1118     const QString url = addressee->custom(QStringLiteral("KOLAB"), QStringLiteral("FreebusyUrl"));
1119     if (!url.isEmpty()) {
1120         setFreeBusyUrl(url);
1121     }
1122 
1123     // Those fields, although defined in Addressee, are not used in KDE
1124     // (e.g. not visible in kaddressbook/addresseeeditorwidget.cpp)
1125     // So it doesn't matter much if we don't have them in the XML.
1126     // mailer, timezone, productId, sortString, agent, rfc2426 name()
1127 
1128     // Things KAddressBook can't handle, so they are saved as unhandled tags:
1129     // initials, children, gender, language
1130 }
1131 
1132 // The loading is: xml -> Contact -> addressee, this is the second part
1133 void Contact::saveTo(KContacts::Addressee *addressee)
1134 {
1135     // TODO: This needs the same set of TODOs as the setFields method
1136     KolabBase::saveTo(addressee);
1137     KContacts::ResourceLocatorUrl url;
1138 
1139     url.setUrl(QUrl(webPage()));
1140 
1141     addressee->setGivenName(givenName());
1142     addressee->setAdditionalName(middleNames());
1143     addressee->setFamilyName(lastName());
1144     addressee->setFormattedName(fullName());
1145     addressee->setPrefix(prefix());
1146     addressee->setSuffix(suffix());
1147     addressee->setOrganization(organization());
1148     addressee->setUrl(url);
1149     addressee->insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-IMAddress"), imAddress());
1150     addressee->setDepartment(department());
1151     addressee->insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Office"), officeLocation());
1152     addressee->insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Profession"), profession());
1153     addressee->setRole(role());
1154     addressee->setTitle(title());
1155     addressee->insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-ManagersName"), managerName());
1156     addressee->insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-AssistantsName"), assistant());
1157     addressee->setNickName(nickName());
1158     addressee->insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-SpousesName"), spouseName());
1159     if (birthday().isValid()) {
1160         addressee->setBirthday(QDateTime(birthday().startOfDay()));
1161     }
1162 
1163     if (anniversary().isValid()) {
1164         addressee->insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Anniversary"), dateToString(anniversary()));
1165     } else {
1166         addressee->removeCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Anniversary"));
1167     }
1168 
1169     addressee->insertCustom(QStringLiteral("KOLAB"), QStringLiteral("FreebusyUrl"), freeBusyUrl());
1170 
1171     // We need to store both the original attachment name and the picture data into the addressee.
1172     // This is important, otherwise we would save the image under another attachment name w/o deleting the original one!
1173     if (!mPicture.isNull()) {
1174         KContacts::Picture picture(mPicture);
1175         addressee->setPhoto(picture);
1176     }
1177     // Note that we must save the filename in all cases, so that removing the picture
1178     // actually deletes the attachment.
1179     addressee->insertCustom(QStringLiteral("KOLAB"), QStringLiteral("PictureAttachmentName"), mPictureAttachmentName);
1180     if (!mLogo.isNull()) {
1181         KContacts::Picture picture(mLogo);
1182         addressee->setLogo(picture);
1183     }
1184     addressee->insertCustom(QStringLiteral("KOLAB"), QStringLiteral("LogoAttachmentName"), mLogoAttachmentName);
1185     if (!mSound.isNull()) {
1186         addressee->setSound(KContacts::Sound(mSound));
1187     }
1188     addressee->insertCustom(QStringLiteral("KOLAB"), QStringLiteral("SoundAttachmentName"), mSoundAttachmentName);
1189 
1190     if (mHasGeo) {
1191         addressee->setGeo(KContacts::Geo(mLatitude, mLongitude));
1192     }
1193 
1194     QStringList emailAddresses;
1195     for (QList<Email>::ConstIterator it = mEmails.constBegin(), end = mEmails.constEnd(); it != end; ++it) {
1196         // we can't do anything with (*it).displayName
1197         emailAddresses.append((*it).smtpAddress);
1198     }
1199     addressee->setEmails(emailAddresses);
1200 
1201     for (QList<Address>::ConstIterator it = mAddresses.constBegin(), end = mAddresses.constEnd(); it != end; ++it) {
1202         KContacts::Address address;
1203         int type = (*it).kdeAddressType;
1204         if (type == -1) { // no kde-specific type available
1205             type = addressTypeFromString((*it).type);
1206             if ((*it).type == mPreferredAddress) {
1207                 type |= KContacts::Address::Pref;
1208             }
1209         }
1210         address.setType(static_cast<KContacts::Address::Type>(type));
1211         address.setStreet((*it).street);
1212         address.setPostOfficeBox((*it).pobox);
1213         address.setLocality((*it).locality);
1214         address.setRegion((*it).region);
1215         address.setPostalCode((*it).postalCode);
1216         address.setCountry((*it).country);
1217         addressee->insertAddress(address);
1218     }
1219 
1220     for (QList<PhoneNumber>::ConstIterator it = mPhoneNumbers.constBegin(), end = mPhoneNumbers.constEnd(); it != end; ++it) {
1221         KContacts::PhoneNumber number;
1222         number.setType(phoneTypeFromString((*it).type));
1223         number.setNumber((*it).number);
1224         addressee->insertPhoneNumber(number);
1225     }
1226 
1227     for (QList<Custom>::ConstIterator it = mCustomList.constBegin(), end = mCustomList.constEnd(); it != end; ++it) {
1228         QString app = (*it).app.isEmpty() ? QStringLiteral("KADDRESSBOOK") : (*it).app;
1229         addressee->insertCustom(app, (*it).name, (*it).value);
1230     }
1231     // qCDebug(PIMKOLAB_LOG) << addressee->customs();
1232 }
1233 
1234 QImage Contact::loadPictureFromAddressee(const KContacts::Picture &picture)
1235 {
1236     QImage img;
1237     if (!picture.isIntern() && !picture.url().isEmpty()) {
1238         qCWarning(PIMKOLAB_LOG) << "external pictures are currently not supported";
1239         // FIXME add kio support to libcalendaring or use libcurl
1240         //     if ( KIO::NetAccess::download( picture.url(), tmpFile, 0 /*no widget known*/ ) ) {
1241         //       img.load( tmpFile );
1242         //       KIO::NetAccess::removeTempFile( tmpFile );
1243         //     }
1244     } else {
1245         img = picture.data();
1246     }
1247     return img;
1248 }
1249 
1250 QByteArray KolabV2::Contact::loadSoundFromAddressee(const KContacts::Sound &sound)
1251 {
1252     QByteArray data;
1253     if (!sound.isIntern() && !sound.url().isEmpty()) {
1254         //     if ( KIO::NetAccess::download( sound.url(), tmpFile, 0 /*no widget known*/ ) ) {
1255         //       QFile f( tmpFile );
1256         //       if ( f.open( QIODevice::ReadOnly ) ) {
1257         //         data = f.readAll();
1258         //         f.close();
1259         //       }
1260         //       KIO::NetAccess::removeTempFile( tmpFile );
1261         //     }
1262     } else {
1263         data = sound.data();
1264     }
1265     return data;
1266 }
1267 
1268 QString KolabV2::Contact::productID() const
1269 {
1270     // TODO: When KAB has the version number in a header file, don't hardcode (Bo)
1271     // Or we could use Addressee::productID? (David)
1272     return QStringLiteral("KAddressBook 3.3, Kolab resource");
1273 }