File indexing completed on 2025-02-16 04:50:27
0001 /* 0002 * SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com> 0003 * 0004 * SPDX-License-Identifier: LGPL-3.0-or-later 0005 */ 0006 0007 #include "kolabobject.h" 0008 #include "libkolab-version.h" 0009 #include "pimkolab_debug.h" 0010 #include "v2helpers.h" 0011 0012 #include <Akonadi/NoteUtils> 0013 0014 #include "conversion/commonconversion.h" 0015 #include "conversion/kabcconversion.h" 0016 #include "conversion/kcalconversion.h" 0017 #include "conversion/kolabconversion.h" 0018 #include "kolabformat/mimeobject.h" 0019 #include "kolabformatV2/contact.h" 0020 #include "kolabformatV2/distributionlist.h" 0021 #include "kolabformatV2/event.h" 0022 #include "kolabformatV2/journal.h" 0023 #include "kolabformatV2/note.h" 0024 #include "kolabformatV2/task.h" 0025 #include "mime/mimeutils.h" 0026 #include <kolabformat.h> 0027 0028 #include <QUrlQuery> 0029 0030 namespace Kolab 0031 { 0032 static inline QString eventKolabType() 0033 { 0034 return QStringLiteral(KOLAB_TYPE_EVENT); 0035 } 0036 0037 static inline QString todoKolabType() 0038 { 0039 return QStringLiteral(KOLAB_TYPE_TASK); 0040 } 0041 0042 static inline QString journalKolabType() 0043 { 0044 return QStringLiteral(KOLAB_TYPE_JOURNAL); 0045 } 0046 0047 static inline QString contactKolabType() 0048 { 0049 return QStringLiteral(KOLAB_TYPE_CONTACT); 0050 } 0051 0052 static inline QString distlistKolabType() 0053 { 0054 return QStringLiteral(KOLAB_TYPE_DISTLIST); 0055 } 0056 0057 static inline QString distlistKolabTypeCompat() 0058 { 0059 return QStringLiteral(KOLAB_TYPE_DISTLIST_V2); 0060 } 0061 0062 static inline QString noteKolabType() 0063 { 0064 return QStringLiteral(KOLAB_TYPE_NOTE); 0065 } 0066 0067 static inline QString configurationKolabType() 0068 { 0069 return QStringLiteral(KOLAB_TYPE_CONFIGURATION); 0070 } 0071 0072 static inline QString dictKolabType() 0073 { 0074 return QStringLiteral(KOLAB_TYPE_DICT); 0075 } 0076 0077 static inline QString freebusyKolabType() 0078 { 0079 return QStringLiteral(KOLAB_TYPE_FREEBUSY); 0080 } 0081 0082 static inline QString relationKolabType() 0083 { 0084 return QStringLiteral(KOLAB_TYPE_RELATION); 0085 } 0086 0087 static inline QString xCalMimeType() 0088 { 0089 return QStringLiteral(MIME_TYPE_XCAL); 0090 } 0091 0092 static inline QString xCardMimeType() 0093 { 0094 return QStringLiteral(MIME_TYPE_XCARD); 0095 } 0096 0097 static inline QString kolabMimeType() 0098 { 0099 return QStringLiteral(MIME_TYPE_KOLAB); 0100 } 0101 0102 KCalendarCore::Event::Ptr readV2EventXML(const QByteArray &xmlData, QStringList &attachments) 0103 { 0104 return fromXML<KCalendarCore::Event::Ptr, KolabV2::Event>(xmlData, attachments); 0105 } 0106 0107 QString ownUrlDecode(QByteArray encodedParam) 0108 { 0109 encodedParam.replace('+', ' '); 0110 return QUrl::fromPercentEncoding(encodedParam); 0111 } 0112 0113 RelationMember parseMemberUrl(const QString &string) 0114 { 0115 if (string.startsWith(QLatin1StringView("urn:uuid:"))) { 0116 RelationMember member; 0117 member.gid = string.mid(9); 0118 return member; 0119 } 0120 QUrl url(QUrl::fromEncoded(string.toLatin1())); 0121 QList<QByteArray> path; 0122 const QList<QByteArray> fragments = url.path(QUrl::FullyEncoded).toLatin1().split('/'); 0123 path.reserve(fragments.count()); 0124 for (const QByteArray &fragment : fragments) { 0125 path.append(ownUrlDecode(fragment).toUtf8()); 0126 } 0127 // qCDebug(PIMKOLAB_LOG) << path; 0128 bool isShared = false; 0129 int start = path.indexOf("user"); 0130 if (start < 0) { 0131 start = path.indexOf("shared"); 0132 isShared = true; 0133 } 0134 if (start < 0) { 0135 qCWarning(PIMKOLAB_LOG) << R"(Couldn't find "user" or "shared" in path: )" << path; 0136 return {}; 0137 } 0138 path = path.mid(start + 1); 0139 if (path.size() < 2) { 0140 qCWarning(PIMKOLAB_LOG) << "Incomplete path: " << path; 0141 return {}; 0142 } 0143 RelationMember member; 0144 if (!isShared) { 0145 member.user = QString::fromUtf8(path.takeFirst()); 0146 } 0147 member.uid = path.takeLast().toLong(); 0148 member.mailbox = path; 0149 QUrlQuery query(url); 0150 member.messageId = ownUrlDecode(query.queryItemValue(QStringLiteral("message-id"), QUrl::FullyEncoded).toUtf8()); 0151 member.subject = ownUrlDecode(query.queryItemValue(QStringLiteral("subject"), QUrl::FullyEncoded).toUtf8()); 0152 member.date = ownUrlDecode(query.queryItemValue(QStringLiteral("date"), QUrl::FullyEncoded).toUtf8()); 0153 // qCDebug(PIMKOLAB_LOG) << member.uid << member.mailbox; 0154 return member; 0155 } 0156 0157 static QByteArray join(const QList<QByteArray> &list, const QByteArray &c) 0158 { 0159 QByteArray result; 0160 for (const QByteArray &a : list) { 0161 result += a + c; 0162 } 0163 result.chop(c.size()); 0164 return result; 0165 } 0166 0167 KOLAB_EXPORT QString generateMemberUrl(const RelationMember &member) 0168 { 0169 if (!member.gid.isEmpty()) { 0170 return QStringLiteral("urn:uuid:%1").arg(member.gid); 0171 } 0172 QUrl url; 0173 url.setScheme(QStringLiteral("imap")); 0174 QList<QByteArray> path; 0175 path << "/"; 0176 if (!member.user.isEmpty()) { 0177 path << "user"; 0178 path << QUrl::toPercentEncoding(member.user); 0179 } else { 0180 path << "shared"; 0181 } 0182 const auto memberMailbox{member.mailbox}; 0183 for (const QByteArray &mb : memberMailbox) { 0184 path << QUrl::toPercentEncoding(QString::fromUtf8(mb)); 0185 } 0186 path << QByteArray::number(member.uid); 0187 url.setPath(QString::fromUtf8('/' + join(path, "/")), QUrl::TolerantMode); 0188 0189 QUrlQuery query; 0190 query.addQueryItem(QStringLiteral("message-id"), member.messageId); 0191 query.addQueryItem(QStringLiteral("subject"), member.subject); 0192 query.addQueryItem(QStringLiteral("date"), member.date); 0193 url.setQuery(query); 0194 0195 return QString::fromLatin1(url.toEncoded()); 0196 } 0197 0198 //@cond PRIVATE 0199 class KolabObjectReaderPrivate 0200 { 0201 public: 0202 KolabObjectReaderPrivate() 0203 : mAddressee(KContacts::Addressee()) 0204 , mObjectType(InvalidObject) 0205 , mVersion(KolabV3) 0206 , mOverrideObjectType(InvalidObject) 0207 { 0208 } 0209 0210 KCalendarCore::Incidence::Ptr mIncidence; 0211 KContacts::Addressee mAddressee; 0212 KContacts::ContactGroup mContactGroup; 0213 KMime::Message::Ptr mNote; 0214 QStringList mDictionary; 0215 QString mDictionaryLanguage; 0216 ObjectType mObjectType; 0217 Version mVersion; 0218 Kolab::Freebusy mFreebusy; 0219 ObjectType mOverrideObjectType; 0220 Version mOverrideVersion; 0221 bool mDoOverrideVersion = false; 0222 Akonadi::Relation mRelation; 0223 Akonadi::Tag mTag; 0224 QStringList mTagMembers; 0225 }; 0226 //@endcond 0227 0228 KolabObjectReader::KolabObjectReader() 0229 : d(new KolabObjectReaderPrivate) 0230 { 0231 } 0232 0233 KolabObjectReader::KolabObjectReader(const KMime::Message::Ptr &msg) 0234 : d(new KolabObjectReaderPrivate) 0235 { 0236 parseMimeMessage(msg); 0237 } 0238 0239 KolabObjectReader::~KolabObjectReader() = default; 0240 0241 void KolabObjectReader::setObjectType(ObjectType type) 0242 { 0243 d->mOverrideObjectType = type; 0244 } 0245 0246 void KolabObjectReader::setVersion(Version version) 0247 { 0248 d->mOverrideVersion = version; 0249 d->mDoOverrideVersion = true; 0250 } 0251 0252 void printMessageDebugInfo(const KMime::Message::Ptr &msg) 0253 { 0254 // TODO replace by Debug stream for Mimemessage 0255 qCDebug(PIMKOLAB_LOG) << "MessageId: " << msg->messageID()->asUnicodeString(); 0256 qCDebug(PIMKOLAB_LOG) << "Subject: " << msg->subject()->asUnicodeString(); 0257 // Debug() << msg->encodedContent(); 0258 } 0259 0260 ObjectType KolabObjectReader::parseMimeMessage(const KMime::Message::Ptr &msg) 0261 { 0262 ErrorHandler::clearErrors(); 0263 d->mObjectType = InvalidObject; 0264 if (msg->contents().isEmpty()) { 0265 qCCritical(PIMKOLAB_LOG) << "message has no contents (we likely failed to parse it correctly)"; 0266 printMessageDebugInfo(msg); 0267 return InvalidObject; 0268 } 0269 const std::string message = msg->encodedContent().toStdString(); 0270 Kolab::MIMEObject mimeObject; 0271 mimeObject.setObjectType(d->mOverrideObjectType); 0272 if (d->mDoOverrideVersion) { 0273 mimeObject.setVersion(d->mOverrideVersion); 0274 } 0275 d->mObjectType = mimeObject.parseMessage(message); 0276 d->mVersion = mimeObject.getVersion(); 0277 switch (mimeObject.getType()) { 0278 case EventObject: { 0279 const Kolab::Event &event = mimeObject.getEvent(); 0280 d->mIncidence = Kolab::Conversion::toKCalendarCore(event); 0281 break; 0282 } 0283 case TodoObject: { 0284 const Kolab::Todo &event = mimeObject.getTodo(); 0285 d->mIncidence = Kolab::Conversion::toKCalendarCore(event); 0286 break; 0287 } 0288 case JournalObject: { 0289 const Kolab::Journal &event = mimeObject.getJournal(); 0290 d->mIncidence = Kolab::Conversion::toKCalendarCore(event); 0291 break; 0292 } 0293 case ContactObject: { 0294 const Kolab::Contact &contact = mimeObject.getContact(); 0295 d->mAddressee = Kolab::Conversion::toKABC(contact); // TODO extract attachments 0296 break; 0297 } 0298 case DistlistObject: { 0299 const Kolab::DistList &distlist = mimeObject.getDistlist(); 0300 d->mContactGroup = Kolab::Conversion::toKABC(distlist); 0301 break; 0302 } 0303 case NoteObject: { 0304 const Kolab::Note ¬e = mimeObject.getNote(); 0305 d->mNote = Kolab::Conversion::toNote(note); 0306 break; 0307 } 0308 case DictionaryConfigurationObject: { 0309 const Kolab::Configuration &configuration = mimeObject.getConfiguration(); 0310 const Kolab::Dictionary &dictionary = configuration.dictionary(); 0311 d->mDictionary.clear(); 0312 const std::vector<std::string> entries = dictionary.entries(); 0313 d->mDictionary.reserve(entries.size()); 0314 for (const std::string &entry : entries) { 0315 d->mDictionary.append(Conversion::fromStdString(entry)); 0316 } 0317 d->mDictionaryLanguage = Conversion::fromStdString(dictionary.language()); 0318 break; 0319 } 0320 case FreebusyObject: { 0321 const Kolab::Freebusy &fb = mimeObject.getFreebusy(); 0322 d->mFreebusy = fb; 0323 break; 0324 } 0325 case RelationConfigurationObject: { 0326 const Kolab::Configuration &configuration = mimeObject.getConfiguration(); 0327 const Kolab::Relation &relation = configuration.relation(); 0328 0329 if (relation.type() == "tag") { 0330 d->mTag = Akonadi::Tag(); 0331 d->mTag.setName(Conversion::fromStdString(relation.name())); 0332 d->mTag.setGid(Conversion::fromStdString(configuration.uid()).toLatin1()); 0333 d->mTag.setType(Akonadi::Tag::GENERIC); 0334 0335 d->mTagMembers.reserve(relation.members().size()); 0336 const auto members{relation.members()}; 0337 for (const std::string &member : members) { 0338 d->mTagMembers << Conversion::fromStdString(member); 0339 } 0340 } else if (relation.type() == "generic") { 0341 if (relation.members().size() == 2) { 0342 d->mRelation = Akonadi::Relation(); 0343 d->mRelation.setRemoteId(Conversion::fromStdString(configuration.uid()).toLatin1()); 0344 d->mRelation.setType(Akonadi::Relation::GENERIC); 0345 0346 d->mTagMembers.reserve(relation.members().size()); 0347 const auto members{relation.members()}; 0348 for (const std::string &member : members) { 0349 d->mTagMembers << Conversion::fromStdString(member); 0350 } 0351 } else { 0352 qCCritical(PIMKOLAB_LOG) << "generic relation had wrong number of members:" << relation.members().size(); 0353 printMessageDebugInfo(msg); 0354 } 0355 } else { 0356 qCCritical(PIMKOLAB_LOG) << "unknown configuration object type" << relation.type(); 0357 printMessageDebugInfo(msg); 0358 } 0359 break; 0360 } 0361 default: 0362 qCCritical(PIMKOLAB_LOG) << "no kolab object found "; 0363 printMessageDebugInfo(msg); 0364 break; 0365 } 0366 return d->mObjectType; 0367 } 0368 0369 Version KolabObjectReader::getVersion() const 0370 { 0371 return d->mVersion; 0372 } 0373 0374 ObjectType KolabObjectReader::getType() const 0375 { 0376 return d->mObjectType; 0377 } 0378 0379 KCalendarCore::Event::Ptr KolabObjectReader::getEvent() const 0380 { 0381 return d->mIncidence.dynamicCast<KCalendarCore::Event>(); 0382 } 0383 0384 KCalendarCore::Todo::Ptr KolabObjectReader::getTodo() const 0385 { 0386 return d->mIncidence.dynamicCast<KCalendarCore::Todo>(); 0387 } 0388 0389 KCalendarCore::Journal::Ptr KolabObjectReader::getJournal() const 0390 { 0391 return d->mIncidence.dynamicCast<KCalendarCore::Journal>(); 0392 } 0393 0394 KCalendarCore::Incidence::Ptr KolabObjectReader::getIncidence() const 0395 { 0396 return d->mIncidence; 0397 } 0398 0399 KContacts::Addressee KolabObjectReader::getContact() const 0400 { 0401 return d->mAddressee; 0402 } 0403 0404 KContacts::ContactGroup KolabObjectReader::getDistlist() const 0405 { 0406 return d->mContactGroup; 0407 } 0408 0409 KMime::Message::Ptr KolabObjectReader::getNote() const 0410 { 0411 return d->mNote; 0412 } 0413 0414 QStringList KolabObjectReader::getDictionary(QString &lang) const 0415 { 0416 lang = d->mDictionaryLanguage; 0417 return d->mDictionary; 0418 } 0419 0420 Freebusy KolabObjectReader::getFreebusy() const 0421 { 0422 return d->mFreebusy; 0423 } 0424 0425 bool KolabObjectReader::isTag() const 0426 { 0427 return !d->mTag.gid().isEmpty(); 0428 } 0429 0430 Akonadi::Tag KolabObjectReader::getTag() const 0431 { 0432 return d->mTag; 0433 } 0434 0435 QStringList KolabObjectReader::getTagMembers() const 0436 { 0437 return d->mTagMembers; 0438 } 0439 0440 bool KolabObjectReader::isRelation() const 0441 { 0442 return d->mRelation.isValid(); 0443 } 0444 0445 Akonadi::Relation KolabObjectReader::getRelation() const 0446 { 0447 return d->mRelation; 0448 } 0449 0450 static KMime::Message::Ptr createMimeMessage(const std::string &mimeMessage) 0451 { 0452 KMime::Message::Ptr msg(new KMime::Message); 0453 msg->setContent(QByteArray(mimeMessage.c_str())); 0454 msg->parse(); 0455 return msg; 0456 } 0457 0458 KMime::Message::Ptr KolabObjectWriter::writeEvent(const KCalendarCore::Event::Ptr &i, Version v, const QString &productId, const QString &) 0459 { 0460 ErrorHandler::clearErrors(); 0461 if (!i) { 0462 qCCritical(PIMKOLAB_LOG) << "passed a null pointer"; 0463 return {}; 0464 } 0465 const Kolab::Event &event = Kolab::Conversion::fromKCalendarCore(*i); 0466 Kolab::MIMEObject mimeObject; 0467 const std::string mimeMessage = mimeObject.writeEvent(event, v, productId.toStdString()); 0468 return createMimeMessage(mimeMessage); 0469 } 0470 0471 KMime::Message::Ptr KolabObjectWriter::writeTodo(const KCalendarCore::Todo::Ptr &i, Version v, const QString &productId, const QString &) 0472 { 0473 ErrorHandler::clearErrors(); 0474 if (!i) { 0475 qCCritical(PIMKOLAB_LOG) << "passed a null pointer"; 0476 return {}; 0477 } 0478 const Kolab::Todo &todo = Kolab::Conversion::fromKCalendarCore(*i); 0479 Kolab::MIMEObject mimeObject; 0480 const std::string mimeMessage = mimeObject.writeTodo(todo, v, productId.toStdString()); 0481 return createMimeMessage(mimeMessage); 0482 } 0483 0484 KMime::Message::Ptr KolabObjectWriter::writeJournal(const KCalendarCore::Journal::Ptr &i, Version v, const QString &productId, const QString &) 0485 { 0486 ErrorHandler::clearErrors(); 0487 if (!i) { 0488 qCCritical(PIMKOLAB_LOG) << "passed a null pointer"; 0489 return {}; 0490 } 0491 const Kolab::Journal &journal = Kolab::Conversion::fromKCalendarCore(*i); 0492 Kolab::MIMEObject mimeObject; 0493 const std::string mimeMessage = mimeObject.writeJournal(journal, v, productId.toStdString()); 0494 return createMimeMessage(mimeMessage); 0495 } 0496 0497 KMime::Message::Ptr KolabObjectWriter::writeIncidence(const KCalendarCore::Incidence::Ptr &i, Version v, const QString &productId, const QString &tz) 0498 { 0499 if (!i) { 0500 qCCritical(PIMKOLAB_LOG) << "passed a null pointer"; 0501 return {}; 0502 } 0503 switch (i->type()) { 0504 case KCalendarCore::IncidenceBase::TypeEvent: 0505 return writeEvent(i.dynamicCast<KCalendarCore::Event>(), v, productId, tz); 0506 case KCalendarCore::IncidenceBase::TypeTodo: 0507 return writeTodo(i.dynamicCast<KCalendarCore::Todo>(), v, productId, tz); 0508 case KCalendarCore::IncidenceBase::TypeJournal: 0509 return writeJournal(i.dynamicCast<KCalendarCore::Journal>(), v, productId, tz); 0510 default: 0511 qCCritical(PIMKOLAB_LOG) << "unknown incidence type"; 0512 } 0513 return {}; 0514 } 0515 0516 KMime::Message::Ptr KolabObjectWriter::writeContact(const KContacts::Addressee &addressee, Version v, const QString &productId) 0517 { 0518 ErrorHandler::clearErrors(); 0519 const Kolab::Contact &contact = Kolab::Conversion::fromKABC(addressee); 0520 Kolab::MIMEObject mimeObject; 0521 const std::string mimeMessage = mimeObject.writeContact(contact, v, productId.toStdString()); 0522 return createMimeMessage(mimeMessage); 0523 } 0524 0525 KMime::Message::Ptr KolabObjectWriter::writeDistlist(const KContacts::ContactGroup &kDistList, Version v, const QString &productId) 0526 { 0527 ErrorHandler::clearErrors(); 0528 const Kolab::DistList &distlist = Kolab::Conversion::fromKABC(kDistList); 0529 Kolab::MIMEObject mimeObject; 0530 const std::string mimeMessage = mimeObject.writeDistlist(distlist, v, productId.toStdString()); 0531 return createMimeMessage(mimeMessage); 0532 } 0533 0534 KMime::Message::Ptr KolabObjectWriter::writeNote(const KMime::Message::Ptr &n, Version v, const QString &productId) 0535 { 0536 ErrorHandler::clearErrors(); 0537 if (!n) { 0538 qCCritical(PIMKOLAB_LOG) << "passed a null pointer"; 0539 return {}; 0540 } 0541 const Kolab::Note ¬e = Kolab::Conversion::fromNote(n); 0542 Kolab::MIMEObject mimeObject; 0543 const std::string mimeMessage = mimeObject.writeNote(note, v, productId.toStdString()); 0544 return createMimeMessage(mimeMessage); 0545 } 0546 0547 KMime::Message::Ptr KolabObjectWriter::writeDictionary(const QStringList &entries, const QString &lang, Version v, const QString &productId) 0548 { 0549 ErrorHandler::clearErrors(); 0550 0551 Kolab::Dictionary dictionary(Conversion::toStdString(lang)); 0552 std::vector<std::string> ent; 0553 ent.reserve(entries.count()); 0554 for (const QString &e : entries) { 0555 ent.push_back(Conversion::toStdString(e)); 0556 } 0557 dictionary.setEntries(ent); 0558 Kolab::Configuration configuration(dictionary); // TODO preserve creation/lastModified date 0559 Kolab::MIMEObject mimeObject; 0560 const std::string mimeMessage = mimeObject.writeConfiguration(configuration, v, productId.toStdString()); 0561 return createMimeMessage(mimeMessage); 0562 } 0563 0564 KMime::Message::Ptr KolabObjectWriter::writeFreebusy(const Freebusy &freebusy, Version v, const QString &productId) 0565 { 0566 ErrorHandler::clearErrors(); 0567 Kolab::MIMEObject mimeObject; 0568 const std::string mimeMessage = mimeObject.writeFreebusy(freebusy, v, productId.toStdString()); 0569 return createMimeMessage(mimeMessage); 0570 } 0571 0572 KMime::Message::Ptr writeRelationHelper(const Kolab::Relation &relation, const QByteArray &uid, const QString &productId) 0573 { 0574 ErrorHandler::clearErrors(); 0575 Kolab::MIMEObject mimeObject; 0576 0577 Kolab::Configuration configuration(relation); // TODO preserve creation/lastModified date 0578 configuration.setUid(uid.constData()); 0579 const std::string mimeMessage = mimeObject.writeConfiguration(configuration, Kolab::KolabV3, Conversion::toStdString(productId)); 0580 return createMimeMessage(mimeMessage); 0581 } 0582 0583 KMime::Message::Ptr KolabObjectWriter::writeTag(const Akonadi::Tag &tag, const QStringList &members, Version v, const QString &productId) 0584 { 0585 ErrorHandler::clearErrors(); 0586 if (v != KolabV3) { 0587 qCCritical(PIMKOLAB_LOG) << "only v3 implementation available"; 0588 } 0589 0590 Kolab::Relation relation(Conversion::toStdString(tag.name()), "tag"); 0591 std::vector<std::string> m; 0592 m.reserve(members.count()); 0593 for (const QString &member : members) { 0594 m.push_back(Conversion::toStdString(member)); 0595 } 0596 relation.setMembers(m); 0597 0598 return writeRelationHelper(relation, tag.gid(), productId); 0599 } 0600 0601 KMime::Message::Ptr KolabObjectWriter::writeRelation(const Akonadi::Relation &relation, const QStringList &items, Version v, const QString &productId) 0602 { 0603 ErrorHandler::clearErrors(); 0604 if (v != KolabV3) { 0605 qCCritical(PIMKOLAB_LOG) << "only v3 implementation available"; 0606 } 0607 0608 if (items.size() != 2) { 0609 qCCritical(PIMKOLAB_LOG) << "Wrong number of members for generic relation."; 0610 return {}; 0611 } 0612 0613 Kolab::Relation kolabRelation(std::string(), "generic"); 0614 std::vector<std::string> m; 0615 m.reserve(2); 0616 m.push_back(Conversion::toStdString(items.at(0))); 0617 m.push_back(Conversion::toStdString(items.at(1))); 0618 kolabRelation.setMembers(m); 0619 0620 return writeRelationHelper(kolabRelation, relation.remoteId(), productId); 0621 } 0622 } // Namespace