File indexing completed on 2024-05-12 05:10:47
0001 /* 0002 SPDX-FileCopyrightText: 2002-2004 Klarälvdalens Datakonsult AB, 0003 <info@klaralvdalens-datakonsult.se> 0004 0005 SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org> 0006 SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0007 0008 SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com> 0009 0010 SPDX-License-Identifier: LGPL-2.0-or-later 0011 */ 0012 0013 #include "itiphandler.h" 0014 #include "calendarsettings.h" 0015 #include "itiphandler_p.h" 0016 #include "itiphandlerhelper_p.h" 0017 #include "mailclient_p.h" 0018 #include "publishdialog.h" 0019 #include "utils_p.h" 0020 0021 #include <KCalUtils/Stringify> 0022 #include <KCalendarCore/Attendee> 0023 #include <KCalendarCore/ICalFormat> 0024 0025 #include <Akonadi/MessageQueueJob> 0026 #include <KIdentityManagementCore/IdentityManager> 0027 0028 #include "akonadicalendar_debug.h" 0029 #include <KMessageBox> 0030 0031 using namespace Akonadi; 0032 0033 // async emittion 0034 static void emitiTipMessageProcessed(ITIPHandler *handler, ITIPHandler::Result resultCode, const QString &errorString) 0035 { 0036 QMetaObject::invokeMethod(handler, 0037 "iTipMessageProcessed", 0038 Qt::QueuedConnection, 0039 Q_ARG(Akonadi::ITIPHandler::Result, resultCode), 0040 Q_ARG(QString, errorString)); 0041 } 0042 0043 GroupwareUiDelegate::GroupwareUiDelegate(QObject *parent) 0044 : QObject(parent) 0045 { 0046 } 0047 0048 GroupwareUiDelegate::~GroupwareUiDelegate() = default; 0049 0050 ITIPHandlerComponentFactory::ITIPHandlerComponentFactory(QObject *parent) 0051 : QObject(parent) 0052 { 0053 } 0054 0055 ITIPHandlerComponentFactory::~ITIPHandlerComponentFactory() = default; 0056 0057 Akonadi::MessageQueueJob *ITIPHandlerComponentFactory::createMessageQueueJob(const KCalendarCore::IncidenceBase::Ptr &incidence, 0058 const KIdentityManagementCore::Identity &identity, 0059 QObject *parent) 0060 { 0061 Q_UNUSED(incidence) 0062 Q_UNUSED(identity) 0063 return new Akonadi::MessageQueueJob(parent); 0064 } 0065 0066 ITIPHandlerDialogDelegate * 0067 ITIPHandlerComponentFactory::createITIPHanderDialogDelegate(const KCalendarCore::Incidence::Ptr &incidence, KCalendarCore::iTIPMethod method, QWidget *parent) 0068 { 0069 return new ITIPHandlerDialogDelegate(incidence, method, parent); 0070 } 0071 0072 ITIPHandler::ITIPHandler(QObject *parent) 0073 : QObject(parent) 0074 , d(new ITIPHandlerPrivate(/*factory=*/nullptr, this)) 0075 { 0076 qRegisterMetaType<Akonadi::ITIPHandler::Result>("Akonadi::ITIPHandler::Result"); 0077 } 0078 0079 ITIPHandler::ITIPHandler(ITIPHandlerComponentFactory *factory, QObject *parent) 0080 : QObject(parent) 0081 , d(new ITIPHandlerPrivate(factory, this)) 0082 { 0083 qRegisterMetaType<Akonadi::ITIPHandler::Result>("Akonadi::ITIPHandler::Result"); 0084 } 0085 0086 ITIPHandler::~ITIPHandler() = default; 0087 0088 void ITIPHandler::processiTIPMessage(const QString &receiver, const QString &iCal, const QString &action) 0089 { 0090 qCDebug(AKONADICALENDAR_LOG) << "processiTIPMessage called with receiver=" << receiver << "; action=" << action; 0091 0092 if (d->m_currentOperation != OperationNone) { 0093 d->m_currentOperation = OperationNone; 0094 qCritical() << "There can't be an operation in progress!" << d->m_currentOperation; 0095 return; 0096 } 0097 0098 d->m_currentOperation = OperationProcessiTIPMessage; 0099 0100 if (!d->isLoaded()) { 0101 d->m_queuedInvitation.receiver = receiver; 0102 d->m_queuedInvitation.iCal = iCal; 0103 d->m_queuedInvitation.action = action; 0104 return; 0105 } 0106 0107 if (d->m_calendarLoadError) { 0108 d->m_currentOperation = OperationNone; 0109 qCritical() << "Error loading calendar"; 0110 emitiTipMessageProcessed(this, ResultError, i18n("Error loading calendar.")); 0111 return; 0112 } 0113 0114 KCalendarCore::ICalFormat format; 0115 KCalendarCore::ScheduleMessage::Ptr message = format.parseScheduleMessage(d->calendar(), iCal); 0116 0117 if (!message) { 0118 const QString errorMessage = format.exception() ? i18n("Error message: %1", KCalUtils::Stringify::errorMessage(*format.exception())) 0119 : i18n("Unknown error while parsing iCal invitation"); 0120 0121 qCritical() << "Error parsing" << errorMessage; 0122 0123 if (d->m_showDialogsOnError) { 0124 KMessageBox::detailedError(nullptr, // mParent, TODO 0125 i18n("Error while processing an invitation or update."), 0126 errorMessage); 0127 } 0128 0129 d->m_currentOperation = OperationNone; 0130 emitiTipMessageProcessed(this, ResultError, errorMessage); 0131 0132 return; 0133 } 0134 0135 d->m_method = static_cast<KCalendarCore::iTIPMethod>(message->method()); 0136 0137 KCalendarCore::ScheduleMessage::Status status = message->status(); 0138 d->m_incidence = message->event().dynamicCast<KCalendarCore::Incidence>(); 0139 if (!d->m_incidence) { 0140 qCritical() << "Invalid incidence"; 0141 d->m_currentOperation = OperationNone; 0142 emitiTipMessageProcessed(this, ResultError, i18n("Invalid incidence")); 0143 return; 0144 } 0145 0146 if (action.startsWith(QLatin1StringView("accepted")) || action.startsWith(QLatin1StringView("tentative")) 0147 || action.startsWith(QLatin1StringView("delegated")) || action.startsWith(QLatin1StringView("counter"))) { 0148 // Find myself and set my status. This can't be done in the scheduler, 0149 // since this does not know the choice I made in the KMail bpf 0150 KCalendarCore::Attendee::List attendees = d->m_incidence->attendees(); 0151 for (auto &attendee : attendees) { 0152 if (attendee.email() == receiver) { 0153 if (action.startsWith(QLatin1StringView("accepted"))) { 0154 attendee.setStatus(KCalendarCore::Attendee::Accepted); 0155 } else if (action.startsWith(QLatin1StringView("tentative"))) { 0156 attendee.setStatus(KCalendarCore::Attendee::Tentative); 0157 } else if (CalendarSettings::self()->outlookCompatCounterProposals() && action.startsWith(QLatin1StringView("counter"))) { 0158 attendee.setStatus(KCalendarCore::Attendee::Tentative); 0159 } else if (action.startsWith(QLatin1StringView("delegated"))) { 0160 attendee.setStatus(KCalendarCore::Attendee::Delegated); 0161 } 0162 break; 0163 } 0164 } 0165 d->m_incidence->setAttendees(attendees); 0166 0167 if (CalendarSettings::self()->outlookCompatCounterProposals() || !action.startsWith(QLatin1StringView("counter"))) { 0168 d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, receiver); 0169 return; // signal emitted in onSchedulerFinished(). 0170 } 0171 // TODO: what happens here? we must emit a signal 0172 } else if (action.startsWith(QLatin1StringView("cancel"))) { 0173 // Delete the old incidence, if one is present 0174 KCalendarCore::Incidence::Ptr existingIncidence = d->calendar()->incidenceFromSchedulingID(d->m_incidence->uid()); 0175 if (existingIncidence) { 0176 d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), KCalendarCore::iTIPCancel, status, receiver); 0177 return; // signal emitted in onSchedulerFinished(). 0178 } else { 0179 // We don't have the incidence, nothing to cancel 0180 qCWarning(AKONADICALENDAR_LOG) << "Couldn't find the incidence to delete.\n" 0181 << "You deleted it previously or didn't even accept the invitation it in the first place.\n" 0182 << "; uid=" << d->m_incidence->uid() << "; identifier=" << d->m_incidence->instanceIdentifier() 0183 << "; summary=" << d->m_incidence->summary(); 0184 0185 qCDebug(AKONADICALENDAR_LOG) << "\n Here's what we do have with such a summary:"; 0186 const KCalendarCore::Incidence::List knownIncidences = calendar()->incidences(); 0187 for (const KCalendarCore::Incidence::Ptr &knownIncidence : knownIncidences) { 0188 if (knownIncidence->summary() == d->m_incidence->summary()) { 0189 qCDebug(AKONADICALENDAR_LOG) << "\nFound: uid=" << knownIncidence->uid() << "; identifier=" << knownIncidence->instanceIdentifier() 0190 << "; schedulingId" << knownIncidence->schedulingID(); 0191 } 0192 } 0193 0194 emitiTipMessageProcessed(this, ResultSuccess, QString()); 0195 } 0196 } else if (action.startsWith(QLatin1StringView("reply"))) { 0197 if (d->m_method != KCalendarCore::iTIPCounter) { 0198 d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, QString()); 0199 } else { 0200 d->m_scheduler->acceptCounterProposal(d->m_incidence, d->calendar()); 0201 } 0202 return; // signal emitted in onSchedulerFinished(). 0203 } else if (action.startsWith(QLatin1StringView("request"))) { 0204 d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, receiver); 0205 return; 0206 } else { 0207 qCritical() << "Unknown incoming action" << action; 0208 0209 d->m_currentOperation = OperationNone; 0210 emitiTipMessageProcessed(this, ResultError, i18n("Invalid action: %1", action)); 0211 } 0212 0213 if (action.startsWith(QLatin1StringView("counter"))) { 0214 if (d->m_uiDelegate) { 0215 Akonadi::Item item; 0216 item.setMimeType(d->m_incidence->mimeType()); 0217 item.setPayload(KCalendarCore::Incidence::Ptr(d->m_incidence->clone())); 0218 0219 // TODO_KDE5: This blocks because m_uiDelegate is not a QObject and can't emit a finished() 0220 // signal. Make async in kde5 0221 d->m_uiDelegate->requestIncidenceEditor(item); 0222 KCalendarCore::Incidence::Ptr newIncidence; 0223 if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) { 0224 newIncidence = item.payload<KCalendarCore::Incidence::Ptr>(); 0225 } 0226 0227 if (*newIncidence == *d->m_incidence) { 0228 emitiTipMessageProcessed(this, ResultCancelled, QString()); 0229 } else { 0230 ITIPHandlerHelper::SendResult result = d->m_helper->sendCounterProposal(receiver, d->m_incidence, newIncidence); 0231 if (result != ITIPHandlerHelper::ResultSuccess) { 0232 // It gives success in all paths, this never happens 0233 emitiTipMessageProcessed(this, ResultError, i18n("Error sending counter proposal")); 0234 Q_ASSERT(false); 0235 } 0236 } 0237 } else { 0238 // This should never happen 0239 qCWarning(AKONADICALENDAR_LOG) << "No UI delegate is set"; 0240 emitiTipMessageProcessed(this, ResultError, i18n("Could not start editor to edit counter proposal")); 0241 } 0242 } 0243 } 0244 0245 void ITIPHandler::sendiTIPMessage(KCalendarCore::iTIPMethod method, const KCalendarCore::Incidence::Ptr &incidence, QWidget *parentWidget) 0246 { 0247 if (!incidence) { 0248 Q_ASSERT(false); 0249 qCritical() << "Invalid incidence"; 0250 return; 0251 } 0252 0253 d->m_queuedInvitation.method = method; 0254 d->m_queuedInvitation.incidence = incidence; 0255 d->m_parentWidget = parentWidget; 0256 0257 if (!d->isLoaded()) { 0258 // This method will be called again once the calendar is loaded. 0259 return; 0260 } 0261 0262 Q_ASSERT(d->m_currentOperation == OperationNone); 0263 if (d->m_currentOperation != OperationNone) { 0264 qCritical() << "There can't be an operation in progress!" << d->m_currentOperation; 0265 return; 0266 } 0267 0268 if (incidence->attendeeCount() == 0 && method != KCalendarCore::iTIPPublish) { 0269 if (d->m_showDialogsOnError) { 0270 KMessageBox::information(parentWidget, 0271 i18n("The item '%1' has no attendees. " 0272 "Therefore no groupware message will be sent.", 0273 incidence->summary()), 0274 i18nc("@title:window", "Message Not Sent"), 0275 QStringLiteral("ScheduleNoAttendees")); 0276 } 0277 0278 return; 0279 } 0280 0281 d->m_currentOperation = OperationSendiTIPMessage; 0282 0283 KCalendarCore::Incidence *incidenceCopy = incidence->clone(); 0284 incidenceCopy->registerObserver(nullptr); 0285 incidenceCopy->clearAttendees(); 0286 0287 d->m_scheduler->performTransaction(incidence, method); 0288 } 0289 0290 void ITIPHandler::publishInformation(const KCalendarCore::Incidence::Ptr &incidence, QWidget *parentWidget) 0291 { 0292 Q_ASSERT(incidence); 0293 if (!incidence) { 0294 qCritical() << "Invalid incidence. Aborting."; 0295 return; 0296 } 0297 0298 Q_ASSERT(d->m_currentOperation == OperationNone); 0299 if (d->m_currentOperation != OperationNone) { 0300 qCritical() << "There can't be an operation in progress!" << d->m_currentOperation; 0301 return; 0302 } 0303 0304 d->m_queuedInvitation.incidence = incidence; 0305 d->m_parentWidget = parentWidget; 0306 0307 d->m_currentOperation = OperationPublishInformation; 0308 0309 QPointer<Akonadi::PublishDialog> publishdlg = new Akonadi::PublishDialog(); 0310 if (incidence->attendeeCount() > 0) { 0311 KCalendarCore::Attendee::List attendees = incidence->attendees(); 0312 KCalendarCore::Attendee::List::ConstIterator it; 0313 KCalendarCore::Attendee::List::ConstIterator end(attendees.constEnd()); 0314 for (it = attendees.constBegin(); it != end; ++it) { 0315 publishdlg->addAttendee(*it); 0316 } 0317 } 0318 if (publishdlg->exec() == QDialog::Accepted && publishdlg) { 0319 d->m_scheduler->publish(incidence, publishdlg->addresses()); 0320 } else { 0321 d->m_currentOperation = OperationNone; 0322 Q_EMIT informationPublished(ResultSuccess, QString()); // Canceled. 0323 } 0324 delete publishdlg; 0325 } 0326 0327 void ITIPHandler::sendAsICalendar(const KCalendarCore::Incidence::Ptr &originalIncidence, QWidget *parentWidget) 0328 { 0329 Q_UNUSED(parentWidget) 0330 Q_ASSERT(originalIncidence); 0331 if (!originalIncidence) { 0332 qCritical() << "Invalid incidence"; 0333 return; 0334 } 0335 0336 // Clone so we can change organizer and recurid 0337 KCalendarCore::Incidence::Ptr incidence = KCalendarCore::Incidence::Ptr(originalIncidence->clone()); 0338 0339 QPointer<Akonadi::PublishDialog> publishdlg = new Akonadi::PublishDialog; 0340 if (publishdlg->exec() == QDialog::Accepted && publishdlg) { 0341 const QString recipients = publishdlg->addresses(); 0342 if (incidence->organizer().isEmpty()) { 0343 incidence->setOrganizer(KCalendarCore::Person(Akonadi::CalendarUtils::fullName(), Akonadi::CalendarUtils::email())); 0344 } 0345 0346 if (incidence->hasRecurrenceId()) { 0347 // For an individual occurrence, recur id doesn't make sense, since we're not sending the whole recurrence series. 0348 incidence->setRecurrenceId({}); 0349 } 0350 0351 KCalendarCore::ICalFormat format; 0352 const QString from = Akonadi::CalendarUtils::email(); 0353 const bool bccMe = Akonadi::CalendarSettings::self()->bcc(); 0354 const QString messageText = format.createScheduleMessage(incidence, KCalendarCore::iTIPRequest); 0355 auto mailer = new MailClient(d->m_factory); 0356 d->m_queuedInvitation.incidence = incidence; 0357 connect(mailer, &MailClient::finished, d.get(), [this](Akonadi::MailClient::Result result, const QString &str) { 0358 d->finishSendAsICalendar(result, str); 0359 }); 0360 0361 mailer->mailTo(incidence, 0362 KIdentityManagementCore::IdentityManager::self()->identityForAddress(from), 0363 from, 0364 KCalendarCore::iTIPRequest, 0365 bccMe, 0366 recipients, 0367 messageText); 0368 } 0369 delete publishdlg; 0370 } 0371 0372 void ITIPHandler::setGroupwareUiDelegate(GroupwareUiDelegate *delegate) 0373 { 0374 d->m_uiDelegate = delegate; 0375 } 0376 0377 void ITIPHandler::setCalendar(const Akonadi::CalendarBase::Ptr &calendar) 0378 { 0379 if (d->m_calendar != calendar) { 0380 d->m_calendar = calendar; 0381 } 0382 } 0383 0384 void ITIPHandler::setShowDialogsOnError(bool enable) 0385 { 0386 d->m_showDialogsOnError = enable; 0387 } 0388 0389 Akonadi::CalendarBase::Ptr ITIPHandler::calendar() const 0390 { 0391 return d->m_calendar; 0392 } 0393 0394 #include "moc_itiphandler.cpp"