File indexing completed on 2024-04-28 05:11:34

0001 /*
0002   SPDX-FileCopyrightText: 2010 Bertjan Broeksema <broeksema@kde.org>
0003   SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0004 
0005   SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include <config-enterprise.h>
0009 
0010 #include "alarmpresets.h"
0011 #include "incidencedefaults.h"
0012 #include "incidenceeditor_debug.h"
0013 
0014 #include <CalendarSupport/KCalPrefs>
0015 #include <akonadi/calendarsettings.h> //krazy:exclude=camelcase this is a generated file
0016 
0017 #include <KContacts/Addressee>
0018 
0019 #include <KCalendarCore/Alarm>
0020 #include <KCalendarCore/Event>
0021 #include <KCalendarCore/Journal>
0022 #include <KCalendarCore/Todo>
0023 
0024 #include <KEmailAddress>
0025 
0026 #include <KIO/StoredTransferJob>
0027 #include <KLocalizedString>
0028 
0029 #include <QFile>
0030 #include <QUrl>
0031 
0032 using namespace CalendarSupport;
0033 using namespace IncidenceEditorNG;
0034 using namespace KCalendarCore;
0035 
0036 namespace IncidenceEditorNG
0037 {
0038 enum { UNSPECIFED_PRIORITY = 0 };
0039 
0040 class IncidenceDefaultsPrivate
0041 {
0042 public:
0043     /// Members
0044     KCalendarCore::Attachment::List mAttachments;
0045     QList<KCalendarCore::Attendee> mAttendees;
0046     QStringList mEmails;
0047     QString mGroupWareDomain;
0048     KCalendarCore::Incidence::Ptr mRelatedIncidence;
0049     QDateTime mStartDt;
0050     QDateTime mEndDt;
0051     bool mCleanupTemporaryFiles;
0052 
0053     /// Methods
0054     [[nodiscard]] KCalendarCore::Person organizerAsPerson() const;
0055     [[nodiscard]] KCalendarCore::Attendee organizerAsAttendee(const KCalendarCore::Person &organizer) const;
0056 
0057     void todoDefaults(const KCalendarCore::Todo::Ptr &todo) const;
0058     void eventDefaults(const KCalendarCore::Event::Ptr &event) const;
0059     void journalDefaults(const KCalendarCore::Journal::Ptr &journal) const;
0060 };
0061 }
0062 
0063 KCalendarCore::Person IncidenceDefaultsPrivate::organizerAsPerson() const
0064 {
0065     const QString invalidEmail = IncidenceDefaults::invalidEmailAddress();
0066 
0067     KCalendarCore::Person organizer;
0068     organizer.setName(i18nc("@label", "no (valid) identities found"));
0069     organizer.setEmail(invalidEmail);
0070 
0071     if (mEmails.isEmpty()) {
0072         // Don't bother any longer, either someone forget to call setFullEmails, or
0073         // the user has no identities configured.
0074         return organizer;
0075     }
0076 
0077     if (!mGroupWareDomain.isEmpty()) {
0078         // Check if we have an identity with an email that ends with the groupware
0079         // domain.
0080         for (const QString &fullEmail : std::as_const(mEmails)) {
0081             QString name;
0082             QString email;
0083             const bool success = KEmailAddress::extractEmailAddressAndName(fullEmail, email, name);
0084             if (success && email.endsWith(mGroupWareDomain)) {
0085                 organizer.setName(name);
0086                 organizer.setEmail(email);
0087                 break;
0088             }
0089         }
0090     }
0091 
0092     if (organizer.email() == invalidEmail) {
0093         // Either, no groupware was used, or we didn't find a groupware email address.
0094         // Now try to
0095         for (const QString &fullEmail : std::as_const(mEmails)) {
0096             QString name;
0097             QString email;
0098             const bool success = KEmailAddress::extractEmailAddressAndName(fullEmail, email, name);
0099             if (success) {
0100                 organizer.setName(name);
0101                 organizer.setEmail(email);
0102                 break;
0103             }
0104         }
0105     }
0106 
0107     return organizer;
0108 }
0109 
0110 KCalendarCore::Attendee IncidenceDefaultsPrivate::organizerAsAttendee(const KCalendarCore::Person &organizer) const
0111 {
0112     KCalendarCore::Attendee organizerAsAttendee;
0113     // Really, the appropriate values (even the fall back values) should come from
0114     // organizer. (See organizerAsPerson for more details).
0115     organizerAsAttendee.setName(organizer.name());
0116     organizerAsAttendee.setEmail(organizer.email());
0117     // NOTE: Don't set the status to None, this value is not supported by the attendee
0118     //       editor atm.
0119     organizerAsAttendee.setStatus(KCalendarCore::Attendee::Accepted);
0120     organizerAsAttendee.setRole(KCalendarCore::Attendee::ReqParticipant);
0121     return organizerAsAttendee;
0122 }
0123 
0124 void IncidenceDefaultsPrivate::eventDefaults(const KCalendarCore::Event::Ptr &event) const
0125 {
0126     QDateTime startDT;
0127     if (mStartDt.isValid()) {
0128         startDT = mStartDt;
0129     } else {
0130         startDT = QDateTime::currentDateTime();
0131 
0132         if (KCalPrefs::instance()->startTime().isValid()) {
0133             startDT.setTime(KCalPrefs::instance()->startTime().time());
0134         }
0135     }
0136 
0137     if (startDT.timeSpec() == Qt::LocalTime) {
0138         // Ensure the default is not "floating"
0139         startDT.setTimeZone(QTimeZone::systemTimeZone());
0140     }
0141 
0142     const QTime defaultDurationTime = KCalPrefs::instance()->defaultDuration().time();
0143     const int defaultDuration = (defaultDurationTime.hour() * 3600) + (defaultDurationTime.minute() * 60);
0144 
0145     QDateTime endDT = mEndDt.isValid() ? mEndDt : startDT.addSecs(defaultDuration);
0146 
0147     if (endDT.timeSpec() == Qt::LocalTime) {
0148         // Ensure the default is not "floating"
0149         endDT.setTimeZone(QTimeZone::systemTimeZone());
0150     }
0151 
0152     event->setDtStart(startDT);
0153     event->setDtEnd(endDT);
0154     event->setTransparency(KCalendarCore::Event::Opaque);
0155 
0156     if (KCalPrefs::instance()->defaultEventReminders()) {
0157         event->addAlarm(AlarmPresets::defaultAlarm(AlarmPresets::BeforeStart));
0158     }
0159 }
0160 
0161 void IncidenceDefaultsPrivate::journalDefaults(const KCalendarCore::Journal::Ptr &journal) const
0162 {
0163     QDateTime startDT = mStartDt.isValid() ? mStartDt : QDateTime::currentDateTime();
0164     if (startDT.timeSpec() == Qt::LocalTime) {
0165         // Ensure the default is not "floating"
0166         startDT.setTimeZone(QTimeZone::systemTimeZone());
0167     }
0168     journal->setDtStart(startDT);
0169     journal->setAllDay(true);
0170 }
0171 
0172 void IncidenceDefaultsPrivate::todoDefaults(const KCalendarCore::Todo::Ptr &todo) const
0173 {
0174     KCalendarCore::Todo::Ptr relatedTodo = mRelatedIncidence.dynamicCast<KCalendarCore::Todo>();
0175     if (relatedTodo) {
0176         todo->setCategories(relatedTodo->categories());
0177     }
0178 
0179     // Now, but not in the "floating" time zone.
0180     auto const systemNow = QDateTime::currentDateTime().toTimeZone(QTimeZone::systemTimeZone());
0181 
0182     if (mEndDt.isValid()) {
0183         if (mEndDt.timeSpec() == Qt::LocalTime) {
0184             // Ensure the default is not "floating"
0185             todo->setDtDue(mEndDt.toTimeZone(QTimeZone::systemTimeZone()), true);
0186         } else {
0187             todo->setDtDue(mEndDt, true /* first */);
0188         }
0189     } else if (relatedTodo && relatedTodo->hasDueDate()) {
0190         todo->setDtDue(relatedTodo->dtDue(true), true /** first */);
0191         todo->setAllDay(relatedTodo->allDay());
0192     } else if (relatedTodo) {
0193         todo->setDtDue(QDateTime());
0194     } else {
0195         todo->setDtDue(systemNow.addDays(1), true /** first */);
0196     }
0197 
0198     if (mStartDt.isValid()) {
0199         if (mStartDt.timeSpec() == Qt::LocalTime) {
0200             // Ensure the default is not "floating"
0201             todo->setDtStart(mStartDt.toTimeZone(QTimeZone::systemTimeZone()));
0202         } else {
0203             todo->setDtStart(mStartDt);
0204         }
0205     } else if (relatedTodo && !relatedTodo->hasStartDate()) {
0206         todo->setDtStart(QDateTime());
0207     } else if (relatedTodo && relatedTodo->hasStartDate() && relatedTodo->dtStart() <= todo->dtDue()) {
0208         todo->setDtStart(relatedTodo->dtStart());
0209         todo->setAllDay(relatedTodo->allDay());
0210     } else if (!mEndDt.isValid() || systemNow < mEndDt) {
0211         todo->setDtStart(systemNow);
0212     } else {
0213         todo->setDtStart(mEndDt.addDays(-1));
0214     }
0215 
0216     todo->setCompleted(false);
0217     todo->setPercentComplete(0);
0218 
0219     // I had a bunch of to-dos and couldn't distinguish between those that had priority '5'
0220     // because I wanted, and those that had priority '5' because it was set by default
0221     // and I forgot to unset it.
0222     // So don't be smart and try to guess a good default priority for the user, just use unspecified.
0223     todo->setPriority(UNSPECIFED_PRIORITY);
0224 
0225     if (KCalPrefs::instance()->defaultTodoReminders()) {
0226         todo->addAlarm(AlarmPresets::defaultAlarm(AlarmPresets::BeforeEnd));
0227     }
0228 }
0229 
0230 /// IncidenceDefaults
0231 
0232 IncidenceDefaults::IncidenceDefaults(bool cleanupAttachmentTemporaryFiles)
0233     : d_ptr(new IncidenceDefaultsPrivate)
0234 {
0235     d_ptr->mCleanupTemporaryFiles = cleanupAttachmentTemporaryFiles;
0236 }
0237 
0238 IncidenceDefaults::IncidenceDefaults(const IncidenceDefaults &other)
0239     : d_ptr(new IncidenceDefaultsPrivate)
0240 {
0241     *d_ptr = *other.d_ptr;
0242 }
0243 
0244 IncidenceDefaults::~IncidenceDefaults() = default;
0245 
0246 IncidenceDefaults &IncidenceDefaults::operator=(const IncidenceDefaults &other)
0247 {
0248     if (&other != this) {
0249         *d_ptr = *other.d_ptr;
0250     }
0251     return *this;
0252 }
0253 
0254 void IncidenceDefaults::setAttachments(const QStringList &attachments,
0255                                        const QStringList &attachmentMimetypes,
0256                                        const QStringList &attachmentLabels,
0257                                        bool inlineAttachment)
0258 {
0259     Q_D(IncidenceDefaults);
0260     d->mAttachments.clear();
0261 
0262     QStringList::ConstIterator it;
0263     int i = 0;
0264     for (it = attachments.constBegin(); it != attachments.constEnd(); ++it, ++i) {
0265         if (!(*it).isEmpty()) {
0266             QString mimeType;
0267             if (attachmentMimetypes.count() > i) {
0268                 mimeType = attachmentMimetypes[i];
0269             }
0270 
0271             KCalendarCore::Attachment attachment;
0272             if (inlineAttachment) {
0273                 auto job = KIO::storedGet(QUrl::fromUserInput(*it));
0274                 if (job->exec()) {
0275                     const QByteArray data = job->data();
0276                     attachment = KCalendarCore::Attachment(data.toBase64(), mimeType);
0277 
0278                     if (i < attachmentLabels.count()) {
0279                         attachment.setLabel(attachmentLabels[i]);
0280                     }
0281                 } else {
0282                     qCCritical(INCIDENCEEDITOR_LOG) << "Error downloading uri " << *it << job->errorString();
0283                 }
0284 
0285                 if (d_ptr->mCleanupTemporaryFiles) {
0286                     QFile file(*it);
0287                     if (!file.remove()) {
0288                         qCCritical(INCIDENCEEDITOR_LOG) << "Uname to remove file " << *it;
0289                     }
0290                 }
0291             } else {
0292                 attachment = KCalendarCore::Attachment(*it, mimeType);
0293                 if (i < attachmentLabels.count()) {
0294                     attachment.setLabel(attachmentLabels[i]);
0295                 }
0296             }
0297 
0298             if (!attachment.isEmpty()) {
0299                 if (attachment.label().isEmpty()) {
0300                     if (attachment.isUri()) {
0301                         attachment.setLabel(attachment.uri());
0302                     } else {
0303                         attachment.setLabel(i18nc("@label attachment contains binary data", "[Binary data]"));
0304                     }
0305                 }
0306                 d->mAttachments << attachment;
0307                 attachment.setShowInline(inlineAttachment);
0308             }
0309         }
0310     }
0311 }
0312 
0313 void IncidenceDefaults::setAttendees(const QStringList &attendees)
0314 {
0315     Q_D(IncidenceDefaults);
0316     d->mAttendees.clear();
0317     QStringList::ConstIterator it;
0318     for (it = attendees.begin(); it != attendees.end(); ++it) {
0319         QString name;
0320         QString email;
0321         KContacts::Addressee::parseEmailAddress(*it, name, email);
0322         d->mAttendees << KCalendarCore::Attendee(name, email, true, KCalendarCore::Attendee::NeedsAction);
0323     }
0324 }
0325 
0326 void IncidenceDefaults::setFullEmails(const QStringList &fullEmails)
0327 {
0328     Q_D(IncidenceDefaults);
0329     d->mEmails = fullEmails;
0330 }
0331 
0332 void IncidenceDefaults::setGroupWareDomain(const QString &domain)
0333 {
0334     Q_D(IncidenceDefaults);
0335     d->mGroupWareDomain = domain;
0336 }
0337 
0338 void IncidenceDefaults::setRelatedIncidence(const KCalendarCore::Incidence::Ptr &incidence)
0339 {
0340     Q_D(IncidenceDefaults);
0341     d->mRelatedIncidence = incidence;
0342 }
0343 
0344 void IncidenceDefaults::setStartDateTime(const QDateTime &startDT)
0345 {
0346     Q_D(IncidenceDefaults);
0347     d->mStartDt = startDT;
0348 }
0349 
0350 void IncidenceDefaults::setEndDateTime(const QDateTime &endDT)
0351 {
0352     Q_D(IncidenceDefaults);
0353     d->mEndDt = endDT;
0354 }
0355 
0356 void IncidenceDefaults::setDefaults(const KCalendarCore::Incidence::Ptr &incidence) const
0357 {
0358     Q_D(const IncidenceDefaults);
0359 
0360     // First some general defaults
0361     incidence->setSummary(QString(), false);
0362     incidence->setLocation(QString(), false);
0363     incidence->setCategories(QStringList());
0364     incidence->setSecrecy(KCalendarCore::Incidence::SecrecyPublic);
0365     incidence->setStatus(KCalendarCore::Incidence::StatusNone);
0366     incidence->setAllDay(false);
0367     incidence->setCustomStatus(QString());
0368     incidence->setResources(QStringList());
0369     incidence->setPriority(0);
0370 
0371     if (d->mRelatedIncidence) {
0372         incidence->setRelatedTo(d->mRelatedIncidence->uid());
0373     }
0374 
0375     incidence->clearAlarms();
0376     incidence->clearAttachments();
0377     incidence->clearAttendees();
0378     incidence->clearComments();
0379     incidence->clearContacts();
0380     incidence->clearRecurrence();
0381 
0382     const KCalendarCore::Person organizerAsPerson = d->organizerAsPerson();
0383 #if KDEPIM_ENTERPRISE_BUILD
0384     incidence->addAttendee(d->organizerAsAttendee(organizerAsPerson));
0385 #endif
0386     for (const KCalendarCore::Attendee &attendee : std::as_const(d->mAttendees)) {
0387         incidence->addAttendee(attendee);
0388     }
0389     // Ical standard: No attendees -> must not have an organizer!
0390     if (incidence->attendeeCount()) {
0391         incidence->setOrganizer(organizerAsPerson);
0392     }
0393 
0394     for (const KCalendarCore::Attachment &attachment : std::as_const(d->mAttachments)) {
0395         incidence->addAttachment(attachment);
0396     }
0397 
0398     switch (incidence->type()) {
0399     case KCalendarCore::Incidence::TypeEvent:
0400         d->eventDefaults(incidence.dynamicCast<KCalendarCore::Event>());
0401         break;
0402     case KCalendarCore::Incidence::TypeTodo:
0403         d->todoDefaults(incidence.dynamicCast<KCalendarCore::Todo>());
0404         break;
0405     case KCalendarCore::Incidence::TypeJournal:
0406         d->journalDefaults(incidence.dynamicCast<KCalendarCore::Journal>());
0407         break;
0408     default:
0409         qCDebug(INCIDENCEEDITOR_LOG) << "Unsupported incidence type, keeping current values. Type: " << static_cast<int>(incidence->type());
0410     }
0411 }
0412 
0413 /** static */
0414 IncidenceDefaults IncidenceDefaults::minimalIncidenceDefaults(bool cleanupAttachmentTempFiles)
0415 {
0416     IncidenceDefaults defaults(cleanupAttachmentTempFiles);
0417 
0418     // Set the full emails manually here, to avoid that we get dependencies on
0419     // KCalPrefs all over the place.
0420     defaults.setFullEmails(CalendarSupport::KCalPrefs::instance()->fullEmails());
0421 
0422     // NOTE: At some point this should be generalized. That is, we now use the
0423     //       freebusy url as a hack, but this assumes that the user has only one
0424     //       groupware account. Which doesn't have to be the case necessarily.
0425     //       This method should somehow depend on the calendar selected to which
0426     //       the incidence is added.
0427     if (CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication()) {
0428         defaults.setGroupWareDomain(QUrl(Akonadi::CalendarSettings::self()->freeBusyRetrieveUrl()).host());
0429     }
0430     return defaults;
0431 }
0432 
0433 /** static */
0434 QString IncidenceDefaults::invalidEmailAddress()
0435 {
0436     static const QString invalidEmail(i18nc("@label invalid email address marker", "invalid@email.address"));
0437     return invalidEmail;
0438 }