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 }